devextreme-schematics 1.2.20 → 1.2.24

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. package/package.json +2 -2
  2. package/src/add-app-template/index.ts +30 -0
  3. package/src/add-app-template/index_spec.ts +73 -0
  4. package/src/add-layout/files/e2e/src/app.e2e-spec.ts +14 -14
  5. package/src/add-layout/files/e2e/src/app.po.ts +11 -11
  6. package/src/add-layout/files/src/app/app-navigation.ts +1 -1
  7. package/src/add-layout/files/src/app/layouts/index.ts +3 -3
  8. package/src/add-layout/files/src/app/layouts/side-nav-inner-toolbar/side-nav-inner-toolbar.component.ts +1 -1
  9. package/src/add-layout/files/src/app/layouts/side-nav-outer-toolbar/side-nav-outer-toolbar.component.ts +1 -1
  10. package/src/add-layout/files/src/app/shared/components/change-password-form/change-password-form.component.html +8 -6
  11. package/src/add-layout/files/src/app/shared/components/create-account-form/create-account-form.component.html +9 -7
  12. package/src/add-layout/files/src/app/shared/components/footer/footer.component.ts +19 -19
  13. package/src/add-layout/files/src/app/shared/components/login-form/login-form.component.html +8 -6
  14. package/src/add-layout/files/src/app/shared/components/reset-password-form/reset-password-form.component.html +8 -6
  15. package/src/add-layout/files/src/dx-styles.scss +4 -0
  16. package/src/add-layout/index.ts +386 -0
  17. package/src/add-layout/index_spec.ts +340 -0
  18. package/src/add-sample-views/files/pages/home/home.component.ts +10 -10
  19. package/src/add-sample-views/files/pages/profile/profile.component.ts +33 -33
  20. package/src/add-sample-views/index.ts +141 -0
  21. package/src/add-sample-views/index_spec.ts +74 -0
  22. package/src/add-view/index.ts +165 -0
  23. package/src/add-view/index_spec.ts +155 -0
  24. package/src/install/index.ts +86 -0
  25. package/src/install/index_spec.ts +106 -0
  26. package/src/install/schema.json +1 -1
  27. package/src/utility/array.ts +3 -0
  28. package/src/utility/change.js +1 -1
  29. package/src/utility/change.ts +66 -0
  30. package/src/utility/latest-versions.js +2 -2
  31. package/src/utility/latest-versions.ts +6 -0
  32. package/src/utility/modify-json-file.ts +13 -0
  33. package/src/utility/project.ts +25 -0
  34. package/src/utility/routing.js +4 -4
  35. package/src/utility/routing.ts +44 -0
  36. package/src/utility/source.ts +16 -0
  37. package/src/utility/string.ts +5 -0
  38. package/src/utility/styles.ts +33 -0
@@ -0,0 +1,386 @@
1
+ import {
2
+ Rule,
3
+ SchematicContext,
4
+ Tree,
5
+ apply,
6
+ url,
7
+ move,
8
+ chain,
9
+ filter,
10
+ forEach,
11
+ mergeWith,
12
+ callRule,
13
+ FileEntry,
14
+ template
15
+ } from '@angular-devkit/schematics';
16
+
17
+ import { of } from 'rxjs';
18
+
19
+ import {
20
+ SourceFile
21
+ } from 'typescript';
22
+
23
+ import { strings } from '@angular-devkit/core';
24
+
25
+ import { join, basename } from 'path';
26
+
27
+ import {
28
+ getApplicationPath,
29
+ getSourceRootPath,
30
+ getProjectName
31
+ } from '../utility/project';
32
+
33
+ import {
34
+ humanize
35
+ } from '../utility/string';
36
+
37
+ import {
38
+ addStylesToApp
39
+ } from '../utility/styles';
40
+
41
+ import {
42
+ modifyJSONFile
43
+ } from '../utility/modify-json-file';
44
+
45
+ import {
46
+ NodeDependencyType,
47
+ addPackageJsonDependency
48
+ } from '@schematics/angular/utility/dependencies';
49
+
50
+ import {
51
+ NodePackageInstallTask
52
+ } from '@angular-devkit/schematics/tasks';
53
+
54
+ import { getSourceFile } from '../utility/source';
55
+
56
+ import {
57
+ applyChanges,
58
+ insertItemToArray
59
+ } from '../utility/change';
60
+
61
+ import {
62
+ hasComponentInRoutes,
63
+ getRoute,
64
+ findRoutesInSource
65
+ } from '../utility/routing';
66
+
67
+ import {
68
+ addImportToModule, addProviderToModule, insertImport
69
+ } from '@schematics/angular/utility/ast-utils';
70
+
71
+ import { getWorkspace } from '@schematics/angular/utility/config';
72
+ import { Change } from '@schematics/angular/utility/change';
73
+
74
+ const projectFilesSource = './files/src';
75
+ const workspaceFilesSource = './files';
76
+
77
+ function addScriptSafe(scripts: any, name: string, value: string) {
78
+ const currentValue = scripts[name];
79
+
80
+ if (!currentValue) {
81
+ scripts[name] = value;
82
+ return;
83
+ }
84
+
85
+ const alterName = `origin-${name}`;
86
+ const safeValue = `npm run ${alterName} && ${value}`;
87
+
88
+ if (currentValue === value || currentValue === safeValue) {
89
+ return;
90
+ }
91
+
92
+ scripts[alterName] = currentValue;
93
+ scripts[name] = safeValue;
94
+ }
95
+
96
+ function addBuildThemeScript() {
97
+ return (host: Tree) => {
98
+ modifyJSONFile(host, './package.json', config => {
99
+ const scripts = config['scripts'];
100
+
101
+ addScriptSafe(scripts, 'build-themes', 'devextreme build');
102
+ addScriptSafe(scripts, 'postinstall', 'npm run build-themes');
103
+
104
+ return config;
105
+ });
106
+
107
+ return host;
108
+ };
109
+ }
110
+
111
+ function addCustomThemeStyles(options: any, sourcePath: string) {
112
+ return (host: Tree) => {
113
+ modifyJSONFile(host, './angular.json', config => {
114
+ const stylesList = [
115
+ `${sourcePath}/dx-styles.scss`,
116
+ `${sourcePath}/themes/generated/theme.additional.css`,
117
+ `${sourcePath}/themes/generated/theme.base.css`,
118
+ 'node_modules/devextreme/dist/css/dx.common.css'
119
+ ];
120
+
121
+ return addStylesToApp(host, options.project, config, stylesList);
122
+ });
123
+
124
+ return host;
125
+ };
126
+ }
127
+
128
+ function updateBudgets(options: any) {
129
+ return (host: Tree) => {
130
+ modifyJSONFile(host, './angular.json', config => {
131
+ const projectName = getProjectName(host, options.project);
132
+ const budgets: any[] = config.projects[projectName].architect.build.configurations.production.budgets;
133
+
134
+ const budget = budgets.find((item) => item.type === 'initial');
135
+ if (budget) {
136
+ budget.maximumWarning = '4mb';
137
+ budget.maximumError = '7mb';
138
+ }
139
+
140
+ return config;
141
+ });
142
+
143
+ return host;
144
+ };
145
+ }
146
+
147
+ function addViewportToBody(sourcePath: string) {
148
+ return (host: Tree) => {
149
+ const indexPath = join(sourcePath, 'index.html');
150
+ let indexContent = host.read(indexPath)!.toString();
151
+
152
+ indexContent = indexContent.replace(/<body>/, '<body class="dx-viewport">');
153
+ host.overwrite(indexPath, indexContent);
154
+
155
+ return host;
156
+ };
157
+ }
158
+
159
+ function modifyFileRule(path: string, callback: (source: SourceFile) => Change[]) {
160
+ return (host: Tree) => {
161
+ const source = getSourceFile(host, path);
162
+
163
+ if (!source) {
164
+ return host;
165
+ }
166
+
167
+ const changes = callback(source);
168
+
169
+ return applyChanges(host, changes, path);
170
+ };
171
+ }
172
+
173
+ function updateAppModule(host: Tree, sourcePath: string) {
174
+ const appModulePath = sourcePath + 'app.module.ts';
175
+
176
+ const importSetter = (importName: string, path: string) => {
177
+ return (source: SourceFile) => {
178
+ return addImportToModule(source, appModulePath, importName, path);
179
+ };
180
+ };
181
+
182
+ const providerSetter = (importName: string, path: string) => {
183
+ return (source: SourceFile) => {
184
+ return addProviderToModule(source, appModulePath, importName, path);
185
+ };
186
+ };
187
+
188
+ const rules = [
189
+ modifyFileRule(appModulePath, importSetter('SideNavOuterToolbarModule', './layouts')),
190
+ modifyFileRule(appModulePath, importSetter('SideNavInnerToolbarModule', './layouts')),
191
+ modifyFileRule(appModulePath, importSetter('SingleCardModule', './layouts')),
192
+ modifyFileRule(appModulePath, importSetter('FooterModule', './shared/components')),
193
+ modifyFileRule(appModulePath, importSetter('ResetPasswordFormModule', './shared/components')),
194
+ modifyFileRule(appModulePath, importSetter('CreateAccountFormModule', './shared/components')),
195
+ modifyFileRule(appModulePath, importSetter('ChangePasswordFormModule', './shared/components')),
196
+ modifyFileRule(appModulePath, importSetter('LoginFormModule', './shared/components')),
197
+ modifyFileRule(appModulePath, providerSetter('AuthService', './shared/services')),
198
+ modifyFileRule(appModulePath, providerSetter('ScreenService', './shared/services')),
199
+ modifyFileRule(appModulePath, providerSetter('AppInfoService', './shared/services')),
200
+ modifyFileRule(appModulePath, importSetter('UnauthenticatedContentModule', './unauthenticated-content')),
201
+ ];
202
+
203
+ if (!hasRoutingModule(host, sourcePath)) {
204
+ rules.push(modifyFileRule(appModulePath, importSetter('AppRoutingModule', './app-routing.module')));
205
+ }
206
+
207
+ return chain(rules);
208
+ }
209
+
210
+ function getComponentName(host: Tree, sourcePath: string) {
211
+ let name = '';
212
+ const index = 1;
213
+
214
+ if (!host.exists(sourcePath + 'app.component.ts')) {
215
+ name = 'app';
216
+ }
217
+
218
+ while (!name) {
219
+ const componentName = `app${index}`;
220
+ if (!host.exists(`${sourcePath}${componentName}.component.ts`)) {
221
+ name = componentName;
222
+ }
223
+ }
224
+
225
+ return name;
226
+ }
227
+
228
+ function hasRoutingModule(host: Tree, sourcePath: string) {
229
+ return host.exists(sourcePath + 'app-routing.module.ts');
230
+ }
231
+
232
+ function addPackagesToDependency(globalNgCliVersion: string) {
233
+ return (host: Tree) => {
234
+ addPackageJsonDependency(host, {
235
+ type: NodeDependencyType.Default,
236
+ name: '@angular/cdk',
237
+ version: globalNgCliVersion
238
+ });
239
+
240
+ return host;
241
+ };
242
+ }
243
+
244
+ function modifyContentByTemplate(
245
+ sourcePath: string,
246
+ templateSourcePath: string,
247
+ filePath: string | null,
248
+ templateOptions: any = {},
249
+ modifyContent?: (templateContent: string, currentContent: string, filePath: string ) => string)
250
+ : Rule {
251
+ return (host: Tree, context: SchematicContext) => {
252
+ const modifyIfExists = (fileEntry: FileEntry) => {
253
+ const fileEntryPath = join(sourcePath, fileEntry.path.toString());
254
+ if (!host.exists(fileEntryPath)) {
255
+ return fileEntry;
256
+ }
257
+
258
+ const templateContent = fileEntry.content!.toString();
259
+ let modifiedContent = templateContent;
260
+
261
+ const currentContent = host.read(fileEntryPath)!.toString();
262
+ if (modifyContent) {
263
+ modifiedContent = modifyContent(templateContent, currentContent, fileEntryPath);
264
+ }
265
+
266
+ // NOTE: Workaround for https://github.com/angular/angular-cli/issues/11337
267
+ if (modifiedContent !== currentContent) {
268
+ host.overwrite(fileEntryPath, modifiedContent);
269
+ }
270
+ return null;
271
+ };
272
+
273
+ const rules = [
274
+ filter(path => {
275
+ return !filePath || join('./', path) === join('./', filePath);
276
+ }),
277
+ template(templateOptions),
278
+ forEach(modifyIfExists),
279
+ move(sourcePath)
280
+ ];
281
+
282
+ const modifiedSource = apply(url(templateSourcePath), rules);
283
+ const resultRule = mergeWith(modifiedSource);
284
+
285
+ return callRule(resultRule, of(host), context);
286
+ };
287
+ }
288
+
289
+ function updateDevextremeConfig(sourcePath: string) {
290
+ const devextremeConfigPath = '/devextreme.json';
291
+ const templateOptions = {
292
+ engine: 'angular',
293
+ sourcePath
294
+ };
295
+
296
+ const modifyConfig = (templateContent: string, currentContent: string) => {
297
+ const oldConfig = JSON.parse(currentContent);
298
+ const newConfig = JSON.parse(templateContent);
299
+
300
+ [].push.apply(oldConfig.build.commands, newConfig.build.commands);
301
+
302
+ return JSON.stringify(oldConfig, null, ' ');
303
+ };
304
+
305
+ return modifyContentByTemplate('./', workspaceFilesSource, devextremeConfigPath, templateOptions, modifyConfig);
306
+ }
307
+
308
+ const modifyRoutingModule = (host: Tree, routingModulePath: string) => {
309
+ // TODO: Try to use the isolated host to generate the result string
310
+ let source = getSourceFile(host, routingModulePath)!;
311
+ const importChange = insertImport(source, routingModulePath, 'LoginFormComponent', './shared/components');
312
+ const providerChanges = addProviderToModule(source, routingModulePath, 'AuthGuardService', './shared/services');
313
+ applyChanges(host, [ importChange, ...providerChanges], routingModulePath);
314
+
315
+ source = getSourceFile(host, routingModulePath)!;
316
+ const routes = findRoutesInSource(source)!;
317
+ if (!hasComponentInRoutes(routes, 'login-form')) {
318
+ const loginFormRoute = getRoute('login-form');
319
+ insertItemToArray(host, routingModulePath, routes, loginFormRoute);
320
+ }
321
+ };
322
+
323
+ export default function(options: any): Rule {
324
+ return (host: Tree) => {
325
+ const project = getProjectName(host, options.project);
326
+ const workspace = getWorkspace(host);
327
+ const prefix = workspace.projects[project].prefix;
328
+ const title = humanize(project);
329
+ const appPath = getApplicationPath(host, project);
330
+ const sourcePath = getSourceRootPath(host, project);
331
+ const layout = options.layout;
332
+ const override = options.resolveConflicts === 'override';
333
+ const componentName = override ? 'app' : getComponentName(host, appPath);
334
+ const pathToCss = sourcePath.replace(/\/?(\w)+\/?/g, '../');
335
+ const templateOptions = {
336
+ name: componentName,
337
+ layout,
338
+ title,
339
+ strings,
340
+ path: pathToCss,
341
+ prefix
342
+ };
343
+
344
+ const modifyContent = (templateContent: string, currentContent: string, filePath: string) => {
345
+ if (basename(filePath) === 'styles.scss') {
346
+ return `${currentContent}\n${templateContent}`;
347
+ }
348
+
349
+ if (basename(filePath) === 'app-routing.module.ts' && hasRoutingModule(host, appPath)) {
350
+ modifyRoutingModule(host, filePath);
351
+ return currentContent;
352
+ }
353
+
354
+ return templateContent;
355
+ };
356
+
357
+ const rules = [
358
+ modifyContentByTemplate(sourcePath, projectFilesSource, null, templateOptions, modifyContent),
359
+ updateDevextremeConfig(sourcePath),
360
+ updateAppModule(host, appPath),
361
+ addBuildThemeScript(),
362
+ addCustomThemeStyles(options, sourcePath),
363
+ addViewportToBody(sourcePath),
364
+ addPackagesToDependency(options.globalNgCliVersion)
365
+ ];
366
+
367
+ if (options.updateBudgets) {
368
+ rules.push(updateBudgets(options));
369
+ }
370
+
371
+ if (!options.skipInstall) {
372
+ rules.push((_: Tree, context: SchematicContext) => {
373
+ context.addTask(new NodePackageInstallTask());
374
+ });
375
+ }
376
+
377
+ if (override) {
378
+ if (project === workspace.defaultProject) {
379
+ rules.push(modifyContentByTemplate('./', workspaceFilesSource, 'e2e/src/app.e2e-spec.ts', { title }));
380
+ rules.push(modifyContentByTemplate('./', workspaceFilesSource, 'e2e/src/app.po.ts'));
381
+ }
382
+ }
383
+
384
+ return chain(rules);
385
+ };
386
+ }