devextreme-schematics 1.2.19 → 1.2.23

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.
Files changed (37) 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/index.ts +386 -0
  16. package/src/add-layout/index_spec.ts +340 -0
  17. package/src/add-sample-views/files/pages/home/home.component.ts +10 -10
  18. package/src/add-sample-views/files/pages/profile/profile.component.ts +33 -33
  19. package/src/add-sample-views/index.ts +141 -0
  20. package/src/add-sample-views/index_spec.ts +74 -0
  21. package/src/add-view/index.ts +165 -0
  22. package/src/add-view/index_spec.ts +155 -0
  23. package/src/install/index.ts +86 -0
  24. package/src/install/index_spec.ts +106 -0
  25. package/src/install/schema.json +1 -1
  26. package/src/utility/array.ts +3 -0
  27. package/src/utility/change.js +1 -1
  28. package/src/utility/change.ts +66 -0
  29. package/src/utility/latest-versions.js +2 -2
  30. package/src/utility/latest-versions.ts +6 -0
  31. package/src/utility/modify-json-file.ts +13 -0
  32. package/src/utility/project.ts +25 -0
  33. package/src/utility/routing.js +4 -4
  34. package/src/utility/routing.ts +44 -0
  35. package/src/utility/source.ts +16 -0
  36. package/src/utility/string.ts +5 -0
  37. 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
+ }