Browse Source

add schematics package to nx workspace

pull/9837/head
mehmet-erim 5 years ago
parent
commit
48ec2890e2
  1. 25
      npm/ng-packs/nx/ng-packs/angular.json
  2. 3
      npm/ng-packs/nx/ng-packs/nx.json
  3. 3
      npm/ng-packs/nx/ng-packs/package.json
  4. 36
      npm/ng-packs/nx/ng-packs/packages/schematics/.eslintrc.json
  5. 18
      npm/ng-packs/nx/ng-packs/packages/schematics/.gitignore
  6. 3
      npm/ng-packs/nx/ng-packs/packages/schematics/.npmignore
  7. 3
      npm/ng-packs/nx/ng-packs/packages/schematics/README.md
  8. 20
      npm/ng-packs/nx/ng-packs/packages/schematics/jest.config.js
  9. 29
      npm/ng-packs/nx/ng-packs/packages/schematics/package.json
  10. 29
      npm/ng-packs/nx/ng-packs/packages/schematics/src/collection.json
  11. 8
      npm/ng-packs/nx/ng-packs/packages/schematics/src/commands/api/files-enum/proxy/__namespace@dir__/__name@kebab__.enum.ts.template
  12. 10
      npm/ng-packs/nx/ng-packs/packages/schematics/src/commands/api/files-model/proxy/__namespace@dir__/models.ts.template
  13. 25
      npm/ng-packs/nx/ng-packs/packages/schematics/src/commands/api/files-service/proxy/__namespace@dir__/__name@kebab__.service.ts.template
  14. 172
      npm/ng-packs/nx/ng-packs/packages/schematics/src/commands/api/index.ts
  15. 45
      npm/ng-packs/nx/ng-packs/packages/schematics/src/commands/api/schema.json
  16. 59
      npm/ng-packs/nx/ng-packs/packages/schematics/src/commands/proxy-add/index.ts
  17. 45
      npm/ng-packs/nx/ng-packs/packages/schematics/src/commands/proxy-add/schema.json
  18. 20
      npm/ng-packs/nx/ng-packs/packages/schematics/src/commands/proxy-index/index.ts
  19. 18
      npm/ng-packs/nx/ng-packs/packages/schematics/src/commands/proxy-index/schema.json
  20. 45
      npm/ng-packs/nx/ng-packs/packages/schematics/src/commands/proxy-refresh/index.ts
  21. 45
      npm/ng-packs/nx/ng-packs/packages/schematics/src/commands/proxy-refresh/schema.json
  22. 50
      npm/ng-packs/nx/ng-packs/packages/schematics/src/commands/proxy-remove/index.ts
  23. 45
      npm/ng-packs/nx/ng-packs/packages/schematics/src/commands/proxy-remove/schema.json
  24. 1
      npm/ng-packs/nx/ng-packs/packages/schematics/src/constants/api.ts
  25. 4
      npm/ng-packs/nx/ng-packs/packages/schematics/src/constants/index.ts
  26. 22
      npm/ng-packs/nx/ng-packs/packages/schematics/src/constants/proxy.ts
  27. 24
      npm/ng-packs/nx/ng-packs/packages/schematics/src/constants/system-types.ts
  28. 1
      npm/ng-packs/nx/ng-packs/packages/schematics/src/constants/volo.ts
  29. 6
      npm/ng-packs/nx/ng-packs/packages/schematics/src/enums/binding-source-id.ts
  30. 16
      npm/ng-packs/nx/ng-packs/packages/schematics/src/enums/exception.ts
  31. 4
      npm/ng-packs/nx/ng-packs/packages/schematics/src/enums/import-keyword.ts
  32. 4
      npm/ng-packs/nx/ng-packs/packages/schematics/src/enums/index.ts
  33. 6
      npm/ng-packs/nx/ng-packs/packages/schematics/src/enums/method-modifier.ts
  34. 1
      npm/ng-packs/nx/ng-packs/packages/schematics/src/index.ts
  35. 7
      npm/ng-packs/nx/ng-packs/packages/schematics/src/lib/schematics.module.ts
  36. 4859
      npm/ng-packs/nx/ng-packs/packages/schematics/src/mocks/api-definition.json
  37. 83
      npm/ng-packs/nx/ng-packs/packages/schematics/src/models/api-definition.ts
  38. 21
      npm/ng-packs/nx/ng-packs/packages/schematics/src/models/generate-proxy-schema.ts
  39. 16
      npm/ng-packs/nx/ng-packs/packages/schematics/src/models/import.ts
  40. 10
      npm/ng-packs/nx/ng-packs/packages/schematics/src/models/index.ts
  41. 84
      npm/ng-packs/nx/ng-packs/packages/schematics/src/models/method.ts
  42. 97
      npm/ng-packs/nx/ng-packs/packages/schematics/src/models/model.ts
  43. 6
      npm/ng-packs/nx/ng-packs/packages/schematics/src/models/project.ts
  44. 5
      npm/ng-packs/nx/ng-packs/packages/schematics/src/models/proxy-config.ts
  45. 27
      npm/ng-packs/nx/ng-packs/packages/schematics/src/models/service.ts
  46. 1
      npm/ng-packs/nx/ng-packs/packages/schematics/src/models/tree.ts
  47. 16
      npm/ng-packs/nx/ng-packs/packages/schematics/src/models/util.ts
  48. 1
      npm/ng-packs/nx/ng-packs/packages/schematics/src/test-setup.ts
  49. 5
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/README.md
  50. 753
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/ast-utils.ts
  51. 127
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/change.ts
  52. 532
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/config.ts
  53. 76
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/dependencies.ts
  54. 151
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/find-module.ts
  55. 17
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/index.ts
  56. 82
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/json-file.ts
  57. 231
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/json-utils.ts
  58. 26
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/latest-versions.ts
  59. 51
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/lint-fix.ts
  60. 87
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/ng-ast-utils.ts
  61. 25
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/parse-name.ts
  62. 19
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/paths.ts
  63. 13
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/project-targets.ts
  64. 70
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/tsconfig.ts
  65. 77
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/validation.ts
  66. 179
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/workspace-models.ts
  67. 91
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/workspace.ts
  68. 6
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/api.ts
  69. 37
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/ast.ts
  70. 99
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/barrel.ts
  71. 35
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/common.ts
  72. 47
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/enum.ts
  73. 8
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/file.ts
  74. 100
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/generics.ts
  75. 11
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/import.ts
  76. 19
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/index.ts
  77. 177
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/model.ts
  78. 20
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/namespace.ts
  79. 39
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/path.ts
  80. 35
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/rule.ts
  81. 122
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/service.ts
  82. 190
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/source.ts
  83. 56
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/text.ts
  84. 71
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/tree.ts
  85. 114
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/type.ts
  86. 66
      npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/workspace.ts
  87. 24
      npm/ng-packs/nx/ng-packs/packages/schematics/tsconfig.json
  88. 10
      npm/ng-packs/nx/ng-packs/packages/schematics/tsconfig.spec.json
  89. 8
      npm/ng-packs/nx/ng-packs/tsconfig.base.json

25
npm/ng-packs/nx/ng-packs/angular.json

@ -407,6 +407,31 @@
}
}
},
"schematics": {
"projectType": "library",
"root": "packages/schematics",
"sourceRoot": "packages/schematics/src",
"prefix": "abp",
"architect": {
"test": {
"builder": "@nrwl/jest:jest",
"outputs": ["coverage/packages/schematics"],
"options": {
"jestConfig": "packages/schematics/jest.config.js",
"passWithNoTests": true
}
},
"lint": {
"builder": "@nrwl/linter:eslint",
"options": {
"lintFilePatterns": [
"packages/schematics/src/**/*.ts",
"packages/schematics/src/**/*.html"
]
}
}
}
},
"setting-management": {
"projectType": "library",
"root": "packages/setting-management",

3
npm/ng-packs/nx/ng-packs/nx.json

@ -65,6 +65,9 @@
"tags": [],
"implicitDependencies": ["core", "theme-shared"]
},
"schematics": {
"tags": []
},
"setting-management": {
"tags": [],
"implicitDependencies": ["core", "theme-shared", "components"]

3
npm/ng-packs/nx/ng-packs/package.json

@ -25,7 +25,8 @@
"update": "nx migrate latest",
"workspace-generator": "nx workspace-generator",
"dep-graph": "nx dep-graph",
"help": "nx help"
"help": "nx help",
"build-all-packages": "nx run-many --target=build --all --exclude=dev-app"
},
"private": true,
"devDependencies": {

36
npm/ng-packs/nx/ng-packs/packages/schematics/.eslintrc.json

@ -0,0 +1,36 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts"],
"extends": [
"plugin:@nrwl/nx/angular",
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "abp",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "abp",
"style": "kebab-case"
}
]
}
},
{
"files": ["*.html"],
"extends": ["plugin:@nrwl/nx/angular-template"],
"rules": {}
}
]
}

18
npm/ng-packs/nx/ng-packs/packages/schematics/.gitignore

@ -0,0 +1,18 @@
# Outputs
src/**/*.js
src/**/*.js.map
src/**/*.d.ts
# IDEs
.idea/
jsconfig.json
.vscode/
# Misc
node_modules/
npm-debug.log*
yarn-error.log*
# Mac OSX Finder files.
**/.DS_Store
.DS_Store

3
npm/ng-packs/nx/ng-packs/packages/schematics/.npmignore

@ -0,0 +1,3 @@
# Ignores TypeScript files, but keeps definitions.
*.ts
!*.d.ts

3
npm/ng-packs/nx/ng-packs/packages/schematics/README.md

@ -0,0 +1,3 @@
# ABP Suite Schematics
TODO: Add usage and development information

20
npm/ng-packs/nx/ng-packs/packages/schematics/jest.config.js

@ -0,0 +1,20 @@
module.exports = {
displayName: 'schematics',
preset: '../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
stringifyContentPathRegex: '\\.(html|svg)$',
},
},
coverageDirectory: '../../coverage/packages/schematics',
transform: {
'^.+\\.(ts|js|html)$': 'jest-preset-angular',
},
snapshotSerializers: [
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment',
],
};

29
npm/ng-packs/nx/ng-packs/packages/schematics/package.json

@ -0,0 +1,29 @@
{
"name": "@abp/ng.schematics",
"version": "4.4.0",
"description": "Schematics that works with ABP Backend",
"keywords": [
"schematics"
],
"author": "",
"license": "MIT",
"schematics": "./collection.json",
"dependencies": {
"@angular-devkit/core": "~11.0.2",
"@angular-devkit/schematics": "~11.0.2",
"got": "^11.5.2",
"jsonc-parser": "^2.3.0",
"should-quote": "^1.0.0",
"typescript": "~3.9.2"
},
"devDependencies": {
"@schematics/angular": "~11.0.2",
"@types/jest": "^26.0.0",
"@types/node": "^12.11.1",
"jest": "^26.0.0",
"jest-preset-angular": "^8.2.0"
},
"publishConfig": {
"access": "public"
}
}

29
npm/ng-packs/nx/ng-packs/packages/schematics/src/collection.json

@ -0,0 +1,29 @@
{
"schematics": {
"proxy-add": {
"description": "ABP Proxy Generator Add Schematics",
"factory": "./commands/proxy-add",
"schema": "./commands/proxy-add/schema.json"
},
"proxy-index": {
"description": "ABP Proxy Generator Index Schematics",
"factory": "./commands/proxy-index",
"schema": "./commands/proxy-index/schema.json"
},
"proxy-refresh": {
"description": "ABP Proxy Generator Refresh Schematics",
"factory": "./commands/proxy-refresh",
"schema": "./commands/proxy-refresh/schema.json"
},
"proxy-remove": {
"description": "ABP Proxy Generator Remove Schematics",
"factory": "./commands/proxy-remove",
"schema": "./commands/proxy-remove/schema.json"
},
"api": {
"description": "ABP API Generator Schematics",
"factory": "./commands/api",
"schema": "./commands/api/schema.json"
}
}
}

8
npm/ng-packs/nx/ng-packs/packages/schematics/src/commands/api/files-enum/proxy/__namespace@dir__/__name@kebab__.enum.ts.template

@ -0,0 +1,8 @@
import { mapEnumToOptions } from '@abp/ng.core';
export enum <%= name %> {<%
for (let member of members) { %>
<%= member.key %> = <%= quote(member.value) %>,<% } %>
}
export const <%= camel(name) %>Options = mapEnumToOptions(<%= name %>);

10
npm/ng-packs/nx/ng-packs/packages/schematics/src/commands/api/files-model/proxy/__namespace@dir__/models.ts.template

@ -0,0 +1,10 @@
<%
for (const {keyword, specifiers, path} of imports) {
%><%= keyword %> { <%= specifiers.join(', ') %> } from '<%= path %>';
<% }
for (let {base, identifier, properties} of interfaces) { %>
export interface <%= identifier %> <%= base ? `extends ${base} ` : '' %>{<%
for (let {name, optional, type} of properties) { %>
<%= name + optional %>: <%= type %>;<% } %>
}
<% } %>

25
npm/ng-packs/nx/ng-packs/packages/schematics/src/commands/api/files-service/proxy/__namespace@dir__/__name@kebab__.service.ts.template

@ -0,0 +1,25 @@
<% for (const {keyword, specifiers, path} of imports) {
%><%= keyword %> { <%= specifiers.join(', ') %> } from '<%= path %>';
<% } %>
@Injectable({
providedIn: 'root',
})
export class <%= name %>Service {
apiName = '<%= apiName %>';<%
for (let {body, signature} of methods) { %>
<%= camel(signature.name) %> = (<%= serializeParameters(signature.parameters) %>) =>
this.restService.request<<%= body.requestType %>, <%= body.responseType %>>({
method: '<%= body.method %>',<%
if (body.responseType === 'string') { %>
responseType: 'text',<% } %>
url: <%= body.url %>,<%
if (body.params.length) { %>
params: { <%= body.params.join(', ') %> },<% }
if (body.body) { %>
body: <%= body.body %>,<% } %>
},
{ apiName: this.apiName });<% } %>
constructor(private restService: RestService) {}
}

172
npm/ng-packs/nx/ng-packs/packages/schematics/src/commands/api/index.ts

@ -0,0 +1,172 @@
import { normalize } from '@angular-devkit/core';
import {
applyTemplates,
branchAndMerge,
chain,
move,
SchematicContext,
SchematicsException,
Tree,
url,
} from '@angular-devkit/schematics';
import { Exception } from '../../enums';
import { GenerateProxySchema, ServiceGeneratorParams } from '../../models';
import {
applyWithOverwrite,
buildDefaultPath,
createControllerToServiceMapper,
createImportRefsToModelReducer,
createImportRefToEnumMapper,
createProxyConfigReader,
createProxyConfigWriterCreator,
createRootNamespaceGetter,
EnumGeneratorParams,
generateProxyConfigJson,
getEnumNamesFromImports,
interpolate,
ModelGeneratorParams,
removeDefaultPlaceholders,
resolveProject,
serializeParameters,
} from '../../utils';
import * as cases from '../../utils/text';
export default function(schema: GenerateProxySchema) {
const params = removeDefaultPlaceholders(schema);
const moduleName = params.module || 'app';
return chain([
async (tree: Tree, _context: SchematicContext) => {
const getRootNamespace = createRootNamespaceGetter(params);
const solution = await getRootNamespace(tree);
const target = await resolveProject(tree, params.target!);
const targetPath = buildDefaultPath(target.definition);
const readProxyConfig = createProxyConfigReader(targetPath);
const createProxyConfigWriter = createProxyConfigWriterCreator(targetPath);
const data = readProxyConfig(tree);
const types = data.types;
const modules = data.modules;
if (!types || !modules) throw new SchematicsException(Exception.InvalidApiDefinition);
const definition = data.modules[moduleName];
if (!definition)
throw new SchematicsException(interpolate(Exception.InvalidModule, moduleName));
const apiName = definition.remoteServiceName;
const controllers = Object.values(definition.controllers || {});
const serviceImports: Record<string, string[]> = {};
const generateServices = createServiceGenerator({
targetPath,
solution,
types,
apiName,
controllers,
serviceImports,
});
const modelImports: Record<string, string[]> = {};
const generateModels = createModelGenerator({
targetPath,
solution,
types,
serviceImports,
modelImports,
});
const generateEnums = createEnumGenerator({
targetPath,
solution,
types,
serviceImports,
modelImports,
});
if (!data.generated.includes(moduleName)) data.generated.push(moduleName);
data.generated.sort();
const json = generateProxyConfigJson(data);
const overwriteProxyConfig = createProxyConfigWriter('overwrite', json);
return branchAndMerge(
chain([generateServices, generateModels, generateEnums, overwriteProxyConfig]),
);
},
]);
}
function createEnumGenerator(params: EnumGeneratorParams) {
const { targetPath, serviceImports, modelImports } = params;
const mapImportRefToEnum = createImportRefToEnumMapper(params);
const enumRefs = [
...new Set([
...getEnumNamesFromImports(serviceImports),
...getEnumNamesFromImports(modelImports),
]),
];
return chain(
enumRefs.map(ref => {
return applyWithOverwrite(url('./files-enum'), [
applyTemplates({
...cases,
...mapImportRefToEnum(ref),
}),
move(normalize(targetPath)),
]);
}),
);
}
function createModelGenerator(params: ModelGeneratorParams) {
const { targetPath, serviceImports, modelImports } = params;
const reduceImportRefsToModels = createImportRefsToModelReducer(params);
const models = Object.values(serviceImports).reduce(reduceImportRefsToModels, []);
models.forEach(({ imports }) =>
imports.forEach(({ refs, path }) =>
refs.forEach(ref => {
if (path === '@abp/ng.core') return;
if (!modelImports[path]) return (modelImports[path] = [ref]);
modelImports[path] = [...new Set([...modelImports[path], ref])];
}),
),
);
return chain(
models.map(model =>
applyWithOverwrite(url('./files-model'), [
applyTemplates({
...cases,
...model,
}),
move(normalize(targetPath)),
]),
),
);
}
function createServiceGenerator(params: ServiceGeneratorParams) {
const { targetPath, controllers, serviceImports } = params;
const mapControllerToService = createControllerToServiceMapper(params);
return chain(
controllers.map(controller => {
const service = mapControllerToService(controller);
service.imports.forEach(({ refs, path }) =>
refs.forEach(ref => {
if (path === '@abp/ng.core') return;
if (!serviceImports[path]) return (serviceImports[path] = [ref]);
serviceImports[path] = [...new Set([...serviceImports[path], ref])];
}),
);
return applyWithOverwrite(url('./files-service'), [
applyTemplates({
...cases,
serializeParameters,
...service,
}),
move(normalize(targetPath)),
]);
}),
);
}

45
npm/ng-packs/nx/ng-packs/packages/schematics/src/commands/api/schema.json

@ -0,0 +1,45 @@
{
"$schema": "http://json-schema.org/schema",
"id": "SchematicsAbpGenerateAPI",
"title": "ABP Generate API Schema",
"type": "object",
"properties": {
"module": {
"description": "Backend module name",
"type": "string",
"$default": {
"$source": "argv",
"index": 0
},
"x-prompt": "Please enter backend module name. (default: \"app\")"
},
"api-name": {
"description": "Backend api name, a.k.a. remoteServiceName",
"type": "string",
"$default": {
"$source": "argv",
"index": 1
},
"x-prompt": "Please enter backend api name, a.k.a. remoteServiceName. (default: \"default\")"
},
"source": {
"description": "Source Angular project for API definition URL & root namespace resolution",
"type": "string",
"$default": {
"$source": "argv",
"index": 2
},
"x-prompt": "Please enter source Angular project for API definition URL & root namespace resolution. (default: workspace \"defaultProject\")"
},
"target": {
"description": "Target Angular project to place the generated code",
"type": "string",
"$default": {
"$source": "argv",
"index": 3
},
"x-prompt": "Please enter target Angular project to place the generated code. (default: workspace \"defaultProject\")"
}
},
"required": []
}

59
npm/ng-packs/nx/ng-packs/packages/schematics/src/commands/proxy-add/index.ts

@ -0,0 +1,59 @@
import { chain, SchematicContext, Tree } from '@angular-devkit/schematics';
import { GenerateProxySchema } from '../../models';
import {
buildDefaultPath,
createApiDefinitionGetter,
createApisGenerator,
createProxyClearer,
createProxyConfigReader,
createProxyConfigSaver,
createProxyIndexGenerator,
createProxyWarningSaver,
mergeAndAllowDelete,
removeDefaultPlaceholders,
resolveProject,
} from '../../utils';
export default function(schema: GenerateProxySchema) {
const params = removeDefaultPlaceholders(schema);
const moduleName = params.module || 'app';
return chain([
async (host: Tree, _context: SchematicContext) => {
const target = await resolveProject(host, params.target!);
const targetPath = buildDefaultPath(target.definition);
const readProxyConfig = createProxyConfigReader(targetPath);
let generated: string[] = [];
try {
generated = readProxyConfig(host).generated;
const index = generated.findIndex(m => m === moduleName);
if (index < 0) generated.push(moduleName);
} catch (_) {
generated.push(moduleName);
}
const getApiDefinition = createApiDefinitionGetter(params);
const data = { generated, ...(await getApiDefinition(host)) };
data.generated = [];
const clearProxy = createProxyClearer(targetPath);
const saveProxyConfig = createProxyConfigSaver(data, targetPath);
const saveProxyWarning = createProxyWarningSaver(targetPath);
const generateApis = createApisGenerator(schema, generated);
const generateIndex = createProxyIndexGenerator(targetPath);
return chain([
mergeAndAllowDelete(host, clearProxy),
saveProxyConfig,
saveProxyWarning,
generateApis,
generateIndex,
]);
},
]);
}

45
npm/ng-packs/nx/ng-packs/packages/schematics/src/commands/proxy-add/schema.json

@ -0,0 +1,45 @@
{
"$schema": "http://json-schema.org/schema",
"id": "SchematicsAbpGenerateProxy",
"title": "ABP Generate Proxy Schema",
"type": "object",
"properties": {
"module": {
"description": "Backend module name",
"type": "string",
"$default": {
"$source": "argv",
"index": 0
},
"x-prompt": "Please enter backend module name. (default: \"app\")"
},
"api-name": {
"description": "Backend api name, a.k.a. remoteServiceName",
"type": "string",
"$default": {
"$source": "argv",
"index": 1
},
"x-prompt": "Please enter backend api name, a.k.a. remoteServiceName. (default: \"default\")"
},
"source": {
"description": "Source Angular project for API definition URL & root namespace resolution",
"type": "string",
"$default": {
"$source": "argv",
"index": 2
},
"x-prompt": "Please enter source Angular project for API definition URL & root namespace resolution. (default: workspace \"defaultProject\")"
},
"target": {
"description": "Target Angular project to place the generated code",
"type": "string",
"$default": {
"$source": "argv",
"index": 3
},
"x-prompt": "Please enter target Angular project to place the generated code. (default: workspace \"defaultProject\")"
}
},
"required": []
}

20
npm/ng-packs/nx/ng-packs/packages/schematics/src/commands/proxy-index/index.ts

@ -0,0 +1,20 @@
import { SchematicContext, Tree } from '@angular-devkit/schematics';
import {
buildDefaultPath,
createProxyIndexGenerator,
removeDefaultPlaceholders,
resolveProject,
} from '../../utils';
export default function(schema: { target?: string }) {
const params = removeDefaultPlaceholders(schema);
return async (host: Tree, _context: SchematicContext) => {
const target = await resolveProject(host, params.target!);
const targetPath = buildDefaultPath(target.definition);
const generateIndex = createProxyIndexGenerator(targetPath);
return generateIndex(host);
};
}

18
npm/ng-packs/nx/ng-packs/packages/schematics/src/commands/proxy-index/schema.json

@ -0,0 +1,18 @@
{
"$schema": "http://json-schema.org/schema",
"id": "SchematicsAbpIndexProxy",
"title": "ABP Index Proxy Schema",
"type": "object",
"properties": {
"target": {
"description": "Target Angular project to place the generated code",
"type": "string",
"$default": {
"$source": "argv",
"index": 0
},
"x-prompt": "Please enter target Angular project to place the generated code. (default: workspace \"defaultProject\")"
}
},
"required": []
}

45
npm/ng-packs/nx/ng-packs/packages/schematics/src/commands/proxy-refresh/index.ts

@ -0,0 +1,45 @@
import { chain, SchematicContext, Tree } from '@angular-devkit/schematics';
import { GenerateProxySchema } from '../../models';
import {
buildDefaultPath,
createApiDefinitionGetter,
createApisGenerator,
createProxyClearer,
createProxyConfigReader,
createProxyConfigSaver,
createProxyIndexGenerator,
mergeAndAllowDelete,
removeDefaultPlaceholders,
resolveProject,
} from '../../utils';
export default function(schema: GenerateProxySchema) {
const params = removeDefaultPlaceholders(schema);
return async (host: Tree, _context: SchematicContext) => {
const target = await resolveProject(host, params.target!);
const targetPath = buildDefaultPath(target.definition);
const readProxyConfig = createProxyConfigReader(targetPath);
const { generated } = readProxyConfig(host);
const getApiDefinition = createApiDefinitionGetter(params);
const data = { generated, ...(await getApiDefinition(host)) };
data.generated = [];
const clearProxy = createProxyClearer(targetPath);
const saveProxyConfig = createProxyConfigSaver(data, targetPath);
const generateApis = createApisGenerator(schema, generated);
const generateIndex = createProxyIndexGenerator(targetPath);
return chain([
mergeAndAllowDelete(host, clearProxy),
saveProxyConfig,
generateApis,
generateIndex,
]);
};
}

45
npm/ng-packs/nx/ng-packs/packages/schematics/src/commands/proxy-refresh/schema.json

@ -0,0 +1,45 @@
{
"$schema": "http://json-schema.org/schema",
"id": "SchematicsAbpGenerateProxy",
"title": "ABP Generate Proxy Schema",
"type": "object",
"properties": {
"module": {
"description": "Backend module name",
"type": "string",
"$default": {
"$source": "argv",
"index": 0
},
"x-prompt": "Please enter backend module name. (default: \"app\")"
},
"api-name": {
"description": "Backend api name, a.k.a. remoteServiceName",
"type": "string",
"$default": {
"$source": "argv",
"index": 1
},
"x-prompt": "Please enter backend api name, a.k.a. remoteServiceName. (default: \"default\")"
},
"source": {
"description": "Source Angular project for API definition URL & root namespace resolution",
"type": "string",
"$default": {
"$source": "argv",
"index": 2
},
"x-prompt": "Please enter source Angular project for API definition URL & root namespace resolution. (default: workspace \"defaultProject\")"
},
"target": {
"description": "Target Angular project to place the generated code",
"type": "string",
"$default": {
"$source": "argv",
"index": 3
},
"x-prompt": "Please enter target Angular project to place the generated code. (default: workspace \"defaultProject\")"
}
},
"required": []
}

50
npm/ng-packs/nx/ng-packs/packages/schematics/src/commands/proxy-remove/index.ts

@ -0,0 +1,50 @@
import { chain, SchematicContext, Tree } from '@angular-devkit/schematics';
import { GenerateProxySchema } from '../../models';
import {
buildDefaultPath,
createApiDefinitionGetter,
createApisGenerator,
createProxyClearer,
createProxyConfigReader,
createProxyConfigSaver,
createProxyIndexGenerator,
mergeAndAllowDelete,
removeDefaultPlaceholders,
resolveProject,
} from '../../utils';
export default function(schema: GenerateProxySchema) {
const params = removeDefaultPlaceholders(schema);
const moduleName = params.module || 'app';
return async (host: Tree, _context: SchematicContext) => {
const target = await resolveProject(host, params.target!);
const targetPath = buildDefaultPath(target.definition);
const readProxyConfig = createProxyConfigReader(targetPath);
const { generated } = readProxyConfig(host);
const index = generated.findIndex(m => m === moduleName);
if (index < 0) return host;
generated.splice(index, 1);
const getApiDefinition = createApiDefinitionGetter(params);
const data = { generated, ...(await getApiDefinition(host)) };
data.generated = [];
const clearProxy = createProxyClearer(targetPath);
const saveProxyConfig = createProxyConfigSaver(data, targetPath);
const generateApis = createApisGenerator(schema, generated);
const generateIndex = createProxyIndexGenerator(targetPath);
return chain([
mergeAndAllowDelete(host, clearProxy),
saveProxyConfig,
generateApis,
generateIndex,
]);
};
}

45
npm/ng-packs/nx/ng-packs/packages/schematics/src/commands/proxy-remove/schema.json

@ -0,0 +1,45 @@
{
"$schema": "http://json-schema.org/schema",
"id": "SchematicsAbpGenerateProxy",
"title": "ABP Generate Proxy Schema",
"type": "object",
"properties": {
"module": {
"description": "Backend module name",
"type": "string",
"$default": {
"$source": "argv",
"index": 0
},
"x-prompt": "Please enter backend module name. (default: \"app\")"
},
"api-name": {
"description": "Backend api name, a.k.a. remoteServiceName",
"type": "string",
"$default": {
"$source": "argv",
"index": 1
},
"x-prompt": "Please enter backend api name, a.k.a. remoteServiceName. (default: \"default\")"
},
"source": {
"description": "Source Angular project for API definition URL & root namespace resolution",
"type": "string",
"$default": {
"$source": "argv",
"index": 2
},
"x-prompt": "Please enter source Angular project for API definition URL & root namespace resolution. (default: workspace \"defaultProject\")"
},
"target": {
"description": "Target Angular project to place the generated code",
"type": "string",
"$default": {
"$source": "argv",
"index": 3
},
"x-prompt": "Please enter target Angular project to place the generated code. (default: workspace \"defaultProject\")"
}
},
"required": []
}

1
npm/ng-packs/nx/ng-packs/packages/schematics/src/constants/api.ts

@ -0,0 +1 @@
export const API_DEFINITION_ENDPOINT = '/api/abp/api-definition';

4
npm/ng-packs/nx/ng-packs/packages/schematics/src/constants/index.ts

@ -0,0 +1,4 @@
export * from './api';
export * from './proxy';
export * from './system-types';
export * from './volo';

22
npm/ng-packs/nx/ng-packs/packages/schematics/src/constants/proxy.ts

@ -0,0 +1,22 @@
export const PROXY_PATH = '/proxy';
export const PROXY_CONFIG_PATH = `${PROXY_PATH}/generate-proxy.json`;
export const PROXY_WARNING_PATH = `${PROXY_PATH}/README.md`;
export const PROXY_WARNING = `# Proxy Generation Output
This directory includes the output of the latest proxy generation.
The files and folders in it will be overwritten when proxy generation is run again.
Therefore, please do not place your own content in this folder.
In addition, \`generate-proxy.json\` works like a lock file.
It includes information used by the proxy generator, so please do not delete or modify it.
Finally, the name of the files and folders should not be changed for two reasons:
- Proxy generator will keep creating them at those paths and you will have multiple copies of the same content.
- ABP Suite generates files which include imports from this folder.
> **Important Notice:** If you are building a module and are planning to publish to npm,
> some of the generated proxies are likely to be exported from public-api.ts file. In such a case,
> please make sure you export files directly and not from barrel exports. In other words,
> do not include index.ts exports in your public-api.ts exports.
`;

24
npm/ng-packs/nx/ng-packs/packages/schematics/src/constants/system-types.ts

@ -0,0 +1,24 @@
export const SYSTEM_TYPES = new Map([
['Bool', 'boolean'],
['Byte', 'number'],
['Char', 'string'],
['Collections.Generic.Dictionary', 'Record'],
['DateTime', 'string'],
['DateTimeOffset', 'string'],
['Decimal', 'number'],
['Double', 'number'],
['Guid', 'string'],
['Int16', 'number'],
['Int32', 'number'],
['Int64', 'number'],
['Net.HttpStatusCode', 'number'],
['Object', 'object'],
['Sbyte', 'number'],
['Single', 'number'],
['String', 'string'],
['TimeSpan', 'string'],
['UInt16', 'number'],
['UInt32', 'number'],
['UInt64', 'number'],
['Void', 'void'],
]);

1
npm/ng-packs/nx/ng-packs/packages/schematics/src/constants/volo.ts

@ -0,0 +1 @@
export const VOLO_REGEX = /^Volo\.Abp\.(Application\.Dtos|ObjectExtending)/;

6
npm/ng-packs/nx/ng-packs/packages/schematics/src/enums/binding-source-id.ts

@ -0,0 +1,6 @@
export enum eBindingSourceId {
Body = 'Body',
Model = 'ModelBinding',
Path = 'Path',
Query = 'Query',
}

16
npm/ng-packs/nx/ng-packs/packages/schematics/src/enums/exception.ts

@ -0,0 +1,16 @@
export const enum Exception {
DirRemoveFailed = '[Directory Remove Failed] Cannot remove "{0}".',
FileNotFound = '[File Not Found] There is no file at "{0}" path.',
FileWriteFailed = '[File Write Failed] Cannot write file at "{0}".',
InvalidModule = '[Invalid Module] Backend module "{0}" does not exist in API definition.',
InvalidApiDefinition = '[Invalid API Definition] The provided API definition is invalid.',
InvalidWorkspace = '[Invalid Workspace] The angular.json should be a valid JSON file.',
NoApi = '[API Not Available] Request to {0} is unsuccessful. Please double-check the URL in the source project environment and make sure your application is up and running.',
NoProject = '[Project Not Found] Either define a default project in your workspace or specify the project name in schematics options.',
NoProxyConfig = '[Proxy Config Not Found] There is no JSON file at "{0}".',
NoTypeDefinition = '[Type Definition Not Found] There is no type definition for "{0}".',
NoWorkspace = '[Workspace Not Found] Make sure you are running schematics at the root directory of your workspace and it has an angular.json file.',
NoEnvironment = '[Environment Not Found] An environment file cannot be located in "{0}" project.',
NoApiUrl = '[API URL Not Found] Cannot resolve API URL for "{1}" remote service name from "{0}" project.',
NoRootNamespace = '[Root Namespace Not Found] Cannot resolve root namespace for "{1}" api from "{0}" project.',
}

4
npm/ng-packs/nx/ng-packs/packages/schematics/src/enums/import-keyword.ts

@ -0,0 +1,4 @@
export enum eImportKeyword {
Default = 'import',
Type = 'import type',
}

4
npm/ng-packs/nx/ng-packs/packages/schematics/src/enums/index.ts

@ -0,0 +1,4 @@
export * from './binding-source-id';
export * from './exception';
export * from './import-keyword';
export * from './method-modifier';

6
npm/ng-packs/nx/ng-packs/packages/schematics/src/enums/method-modifier.ts

@ -0,0 +1,6 @@
export enum eMethodModifier {
Public = '',
Private = 'private ',
Async = 'async ',
PrivateAsync = 'private async ',
}

1
npm/ng-packs/nx/ng-packs/packages/schematics/src/index.ts

@ -0,0 +1 @@
export {};

7
npm/ng-packs/nx/ng-packs/packages/schematics/src/lib/schematics.module.ts

@ -0,0 +1,7 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
imports: [CommonModule],
})
export class SchematicsModule {}

4859
npm/ng-packs/nx/ng-packs/packages/schematics/src/mocks/api-definition.json

File diff suppressed because it is too large

83
npm/ng-packs/nx/ng-packs/packages/schematics/src/models/api-definition.ts

@ -0,0 +1,83 @@
import { eBindingSourceId } from '../enums';
export interface ApiDefinition {
modules: Record<string, Module>;
types: Record<string, Type>;
}
export interface Type {
baseType: string | null;
isEnum: boolean;
enumNames: string[] | null;
enumValues: number[] | null;
genericArguments: string[] | null;
properties: PropertyDef[] | null;
}
export interface PropertyDef {
name: string;
jsonName: string | null;
type: string;
typeSimple: string;
isRequired: boolean;
}
export interface Module {
rootPath: string;
remoteServiceName: string;
controllers: Record<string, Controller>;
}
export interface Controller {
controllerName: string;
type: string;
interfaces: InterfaceDef[];
actions: Record<string, Action>;
}
export interface InterfaceDef {
type: string;
}
export interface Action {
uniqueName: string;
name: string;
httpMethod: string;
url: string;
supportedVersions: string[];
parametersOnMethod: ParameterInSignature[];
parameters: ParameterInBody[];
returnValue: TypeDef;
}
export interface ParameterInSignature {
name: string;
typeAsString: string;
type: string;
typeSimple: string;
isOptional: boolean;
defaultValue: any;
}
export interface ParameterInBody {
nameOnMethod: string;
name: string;
jsonName: string | null;
type: string;
typeSimple: string;
isOptional: boolean;
defaultValue: any;
constraintTypes: string[] | null;
bindingSourceId: eBindingSourceId;
descriptorName: string;
}
export interface TypeDef {
type: string;
typeSimple: string;
}
export interface TypeWithEnum {
isEnum: boolean;
type: string;
}

21
npm/ng-packs/nx/ng-packs/packages/schematics/src/models/generate-proxy-schema.ts

@ -0,0 +1,21 @@
export interface GenerateProxySchema {
/**
* Backend module name
*/
module?: string;
/**
* Backend api name, a.k.a. remoteServiceName
*/
['api-name']?: string;
/**
* Source Angular project for API definition URL & root namespace resolution
*/
source?: string;
/**
* Target Angular project to place the generated code
*/
target?: string;
}

16
npm/ng-packs/nx/ng-packs/packages/schematics/src/models/import.ts

@ -0,0 +1,16 @@
import { eImportKeyword } from '../enums';
import { Omissible } from './util';
export class Import {
alias?: string;
keyword = eImportKeyword.Default;
path: string;
refs: string[] = [];
specifiers: string[] = [];
constructor(options: ImportOptions) {
Object.assign(this, options);
}
}
export type ImportOptions = Omissible<Import, 'keyword' | 'refs' | 'specifiers'>;

10
npm/ng-packs/nx/ng-packs/packages/schematics/src/models/index.ts

@ -0,0 +1,10 @@
export * from './api-definition';
export * from './generate-proxy-schema';
export * from './import';
export * from './method';
export * from './model';
export * from './project';
export * from './proxy-config';
export * from './service';
export * from './tree';
export * from './util';

84
npm/ng-packs/nx/ng-packs/packages/schematics/src/models/method.ts

@ -0,0 +1,84 @@
import { eBindingSourceId, eMethodModifier } from '../enums';
import { camel } from '../utils/text';
import { ParameterInBody } from './api-definition';
import { Property } from './model';
import { Omissible } from './util';
const shouldQuote = require('should-quote');
export class Method {
body: Body;
signature: Signature;
constructor(options: MethodOptions) {
Object.assign(this, options);
}
}
export type MethodOptions = Method;
export class Signature {
generics = '';
modifier = eMethodModifier.Public;
name: string;
parameters: Property[] = [];
returnType = '';
constructor(options: SignatureOptions) {
Object.assign(this, options);
}
}
export type SignatureOptions = Omissible<
Signature,
'generics' | 'modifier' | 'parameters' | 'returnType'
>;
export class Body {
body?: string;
method: string;
params: string[] = [];
requestType = 'any';
responseType: string;
url: string;
registerActionParameter = (param: ParameterInBody) => {
const { bindingSourceId, descriptorName, jsonName, name, nameOnMethod } = param;
const camelName = camel(name);
const paramName = jsonName || camelName;
const value = descriptorName
? shouldQuote(paramName)
? `${descriptorName}['${paramName}']`
: `${descriptorName}.${paramName}`
: nameOnMethod;
switch (bindingSourceId) {
case eBindingSourceId.Model:
case eBindingSourceId.Query:
this.params.push(paramName === value ? value : `${paramName}: ${value}`);
break;
case eBindingSourceId.Body:
this.body = value;
break;
case eBindingSourceId.Path:
const regex = new RegExp('{(' + paramName + '|' + camelName + '|' + name + ')}', 'g');
this.url = this.url.replace(regex, '${' + value + '}');
break;
default:
break;
}
};
constructor(options: BodyOptions) {
Object.assign(this, options);
this.setUrlQuotes();
}
private setUrlQuotes() {
this.url = /{/.test(this.url) ? `\`/${this.url}\`` : `'/${this.url}'`;
}
}
export type BodyOptions = Omissible<
Omit<Body, 'registerActionParameter'>,
'params' | 'requestType'
>;

97
npm/ng-packs/nx/ng-packs/packages/schematics/src/models/model.ts

@ -0,0 +1,97 @@
import { Import } from './import';
import { Options } from './util';
export class Model {
readonly imports: Import[] = [];
readonly interfaces: Interface[] = [];
readonly namespace: string;
readonly path: string;
constructor(options: ModelOptions) {
Object.assign(this, options);
}
}
export type ModelOptions = Options<Model, 'imports' | 'interfaces'>;
export class Interface {
readonly base: string | null;
readonly identifier: string;
readonly namespace: string;
readonly generics: Generic[] = [];
readonly properties: Property[] = [];
readonly ref: string;
constructor(options: InterfaceOptions) {
Object.assign(this, options);
}
}
export type InterfaceOptions = Options<Interface, 'generics' | 'properties'>;
abstract class TypeRef {
readonly refs: string[] = [];
protected _type = '';
get type() {
return this._type;
}
set type(value: string) {
if (!value) return;
this._type = value;
}
protected _default = '';
get default() {
return this._default;
}
set default(value: string) {
if (!value) return;
this._default = ` = ${value}`;
}
constructor(options: TypeRefOptions) {
Object.assign(this, options);
}
setDefault(value: string) {
this.default = value;
}
setType(value: string) {
this.type = value;
}
}
type TypeRefOptionalKeys = 'default' | 'refs';
type TypeRefOptions = Options<TypeRef, TypeRefOptionalKeys>;
export class Generic extends TypeRef {
constructor(options: GenericOptions) {
super(options);
}
}
export type GenericOptions = Options<Generic, TypeRefOptionalKeys>;
export class Property extends TypeRef {
readonly name: string;
private _optional: '' | '?' = '';
get optional() {
return this.default ? '' : this._optional;
}
set optional(value: '' | '?') {
this._optional = value;
}
constructor(options: PropertyOptions) {
super(options);
}
setOptional(isOptional: boolean) {
this.optional = isOptional ? '?' : '';
}
}
export type PropertyOptions = Options<Property, TypeRefOptionalKeys | 'optional'>;

6
npm/ng-packs/nx/ng-packs/packages/schematics/src/models/project.ts

@ -0,0 +1,6 @@
import { workspaces } from '@angular-devkit/core';
export interface Project {
name: string;
definition: workspaces.ProjectDefinition;
}

5
npm/ng-packs/nx/ng-packs/packages/schematics/src/models/proxy-config.ts

@ -0,0 +1,5 @@
import { ApiDefinition } from './api-definition';
export interface ProxyConfig extends ApiDefinition {
generated: string[];
}

27
npm/ng-packs/nx/ng-packs/packages/schematics/src/models/service.ts

@ -0,0 +1,27 @@
import { Controller, Type } from './api-definition';
import { Import } from './import';
import { Method } from './method';
import { Omissible } from './util';
export interface ServiceGeneratorParams {
targetPath: string;
solution: string;
types: Record<string, Type>;
apiName: string;
controllers: Controller[];
serviceImports: Record<string, string[]>;
}
export class Service {
apiName: string;
imports: Import[] = [];
methods: Method[] = [];
name: string;
namespace: string;
constructor(options: ServiceOptions) {
Object.assign(this, options);
}
}
export type ServiceOptions = Omissible<Service, 'imports' | 'methods'>;

1
npm/ng-packs/nx/ng-packs/packages/schematics/src/models/tree.ts

@ -0,0 +1 @@
export type WriteOp = 'create' | 'overwrite';

16
npm/ng-packs/nx/ng-packs/packages/schematics/src/models/util.ts

@ -0,0 +1,16 @@
// Omissible (given keys will become optional)
export type Omissible<T, K extends keyof T> = Partial<Pick<T, K>> & Omit<T, K>;
// ExcludeKeys (keys will be excluded based on their type)
type ExcludeKeys<Type, Excluded> = Exclude<
{
[Key in keyof Type]: Type[Key] extends Excluded ? never : Key;
}[keyof Type],
never
>;
// tslint:disable-next-line: ban-types
type ExcludeMethods<Type> = Pick<Type, ExcludeKeys<Type, Function>>;
// Options (methods will be omitted, given keys will become optional)
export type Options<T, K extends keyof ExcludeMethods<T>> = Omissible<ExcludeMethods<T>, K>;

1
npm/ng-packs/nx/ng-packs/packages/schematics/src/test-setup.ts

@ -0,0 +1 @@
import 'jest-preset-angular/setup-jest';

5
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/README.md

@ -0,0 +1,5 @@
**DISCLAIMER**
This directory is a direct copy of https://github.com/angular/angular-cli/tree/master/packages/schematics/angular/utility and is used under terms and permissions by the MIT license granted by Google, Inc.
All credits go to Angular team for building these utilities.

753
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/ast-utils.ts

@ -0,0 +1,753 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import { Change, InsertChange, NoopChange } from './change';
/**
* Add Import `import { symbolName } from fileName` if the import doesn't exit
* already. Assumes fileToEdit can be resolved and accessed.
* @param fileToEdit (file we want to add import to)
* @param symbolName (item to import)
* @param fileName (path to the file)
* @param isDefault (if true, import follows style for importing default exports)
* @return Change
*/
export function insertImport(source: ts.SourceFile, fileToEdit: string, symbolName: string,
fileName: string, isDefault = false): Change {
const rootNode = source;
const allImports = findNodes(rootNode, ts.SyntaxKind.ImportDeclaration);
// get nodes that map to import statements from the file fileName
const relevantImports = allImports.filter(node => {
// StringLiteral of the ImportDeclaration is the import file (fileName in this case).
const importFiles = node.getChildren()
.filter(ts.isStringLiteral)
.map(n => n.text);
return importFiles.filter(file => file === fileName).length === 1;
});
if (relevantImports.length > 0) {
let importsAsterisk = false;
// imports from import file
const imports: ts.Node[] = [];
relevantImports.forEach(n => {
Array.prototype.push.apply(imports, findNodes(n, ts.SyntaxKind.Identifier));
if (findNodes(n, ts.SyntaxKind.AsteriskToken).length > 0) {
importsAsterisk = true;
}
});
// if imports * from fileName, don't add symbolName
if (importsAsterisk) {
return new NoopChange();
}
const importTextNodes = imports.filter(n => (n as ts.Identifier).text === symbolName);
// insert import if it's not there
if (importTextNodes.length === 0) {
const fallbackPos =
findNodes(relevantImports[0], ts.SyntaxKind.CloseBraceToken)[0].getStart() ||
findNodes(relevantImports[0], ts.SyntaxKind.FromKeyword)[0].getStart();
return insertAfterLastOccurrence(imports, `, ${symbolName}`, fileToEdit, fallbackPos);
}
return new NoopChange();
}
// no such import declaration exists
const useStrict = findNodes(rootNode, ts.isStringLiteral)
.filter((n) => n.text === 'use strict');
let fallbackPos = 0;
if (useStrict.length > 0) {
fallbackPos = useStrict[0].end;
}
const open = isDefault ? '' : '{ ';
const close = isDefault ? '' : ' }';
// if there are no imports or 'use strict' statement, insert import at beginning of file
const insertAtBeginning = allImports.length === 0 && useStrict.length === 0;
const separator = insertAtBeginning ? '' : ';\n';
const toInsert = `${separator}import ${open}${symbolName}${close}` +
` from '${fileName}'${insertAtBeginning ? ';\n' : ''}`;
return insertAfterLastOccurrence(
allImports,
toInsert,
fileToEdit,
fallbackPos,
ts.SyntaxKind.StringLiteral,
);
}
/**
* Find all nodes from the AST in the subtree of node of SyntaxKind kind.
* @param node
* @param kind
* @param max The maximum number of items to return.
* @param recursive Continue looking for nodes of kind recursive until end
* the last child even when node of kind has been found.
* @return all nodes of kind, or [] if none is found
*/
export function findNodes(node: ts.Node, kind: ts.SyntaxKind, max?: number, recursive?: boolean): ts.Node[];
/**
* Find all nodes from the AST in the subtree that satisfy a type guard.
* @param node
* @param guard
* @param max The maximum number of items to return.
* @param recursive Continue looking for nodes of kind recursive until end
* the last child even when node of kind has been found.
* @return all nodes that satisfy the type guard, or [] if none is found
*/
export function findNodes<T extends ts.Node>(node: ts.Node, guard: (node: ts.Node) => node is T, max?: number, recursive?: boolean): T[];
export function findNodes<T extends ts.Node>(
node: ts.Node,
kindOrGuard: ts.SyntaxKind | ((node: ts.Node) => node is T),
max = Infinity,
recursive = false,
): T[] {
if (!node || max == 0) {
return [];
}
const test =
typeof kindOrGuard === 'function'
? kindOrGuard
: (node: ts.Node): node is T => node.kind === kindOrGuard;
const arr: T[] = [];
if (test(node)) {
arr.push(node);
max--;
}
if (max > 0 && (recursive || !test(node))) {
for (const child of node.getChildren()) {
findNodes(child, test, max).forEach((node) => {
if (max > 0) {
arr.push(node);
}
max--;
});
if (max <= 0) {
break;
}
}
}
return arr;
}
/**
* Get all the nodes from a source.
* @param sourceFile The source file object.
* @returns {Array<ts.Node>} An array of all the nodes in the source.
*/
export function getSourceNodes(sourceFile: ts.SourceFile): ts.Node[] {
const nodes: ts.Node[] = [sourceFile];
const result = [];
while (nodes.length > 0) {
const node = nodes.shift();
if (node) {
result.push(node);
if (node.getChildCount(sourceFile) >= 0) {
nodes.unshift(...node.getChildren());
}
}
}
return result;
}
export function findNode(node: ts.Node, kind: ts.SyntaxKind, text: string): ts.Node | null {
if (node.kind === kind && node.getText() === text) {
// throw new Error(node.getText());
return node;
}
let foundNode: ts.Node | null = null;
ts.forEachChild(node, childNode => {
foundNode = foundNode || findNode(childNode, kind, text);
});
return foundNode;
}
/**
* Helper for sorting nodes.
* @return function to sort nodes in increasing order of position in sourceFile
*/
function nodesByPosition(first: ts.Node, second: ts.Node): number {
return first.getStart() - second.getStart();
}
/**
* Insert `toInsert` after the last occurence of `ts.SyntaxKind[nodes[i].kind]`
* or after the last of occurence of `syntaxKind` if the last occurence is a sub child
* of ts.SyntaxKind[nodes[i].kind] and save the changes in file.
*
* @param nodes insert after the last occurence of nodes
* @param toInsert string to insert
* @param file file to insert changes into
* @param fallbackPos position to insert if toInsert happens to be the first occurence
* @param syntaxKind the ts.SyntaxKind of the subchildren to insert after
* @return Change instance
* @throw Error if toInsert is first occurence but fall back is not set
*/
export function insertAfterLastOccurrence(nodes: ts.Node[],
toInsert: string,
file: string,
fallbackPos: number,
syntaxKind?: ts.SyntaxKind): Change {
let lastItem: ts.Node | undefined;
for (const node of nodes) {
if (!lastItem || lastItem.getStart() < node.getStart()) {
lastItem = node;
}
}
if (syntaxKind && lastItem) {
lastItem = findNodes(lastItem, syntaxKind).sort(nodesByPosition).pop();
}
if (!lastItem && fallbackPos == undefined) {
throw new Error(`tried to insert ${toInsert} as first occurence with no fallback position`);
}
const lastItemPosition: number = lastItem ? lastItem.getEnd() : fallbackPos;
return new InsertChange(file, lastItemPosition, toInsert);
}
export function getContentOfKeyLiteral(_source: ts.SourceFile, node: ts.Node): string | null {
if (node.kind == ts.SyntaxKind.Identifier) {
return (node as ts.Identifier).text;
} else if (node.kind == ts.SyntaxKind.StringLiteral) {
return (node as ts.StringLiteral).text;
} else {
return null;
}
}
function _angularImportsFromNode(node: ts.ImportDeclaration,
_sourceFile: ts.SourceFile): {[name: string]: string} {
const ms = node.moduleSpecifier;
let modulePath: string;
switch (ms.kind) {
case ts.SyntaxKind.StringLiteral:
modulePath = (ms as ts.StringLiteral).text;
break;
default:
return {};
}
if (!modulePath.startsWith('@angular/')) {
return {};
}
if (node.importClause) {
if (node.importClause.name) {
// This is of the form `import Name from 'path'`. Ignore.
return {};
} else if (node.importClause.namedBindings) {
const nb = node.importClause.namedBindings;
if (nb.kind == ts.SyntaxKind.NamespaceImport) {
// This is of the form `import * as name from 'path'`. Return `name.`.
return {
[(nb as ts.NamespaceImport).name.text + '.']: modulePath,
};
} else {
// This is of the form `import {a,b,c} from 'path'`
const namedImports = nb as ts.NamedImports;
return namedImports.elements
.map((is: ts.ImportSpecifier) => is.propertyName ? is.propertyName.text : is.name.text)
.reduce((acc: {[name: string]: string}, curr: string) => {
acc[curr] = modulePath;
return acc;
}, {});
}
}
return {};
} else {
// This is of the form `import 'path';`. Nothing to do.
return {};
}
}
export function getDecoratorMetadata(source: ts.SourceFile, identifier: string,
module: string): ts.Node[] {
const angularImports = findNodes(source, ts.isImportDeclaration)
.map((node) => _angularImportsFromNode(node, source))
.reduce((acc, current) => {
for (const key of Object.keys(current)) {
acc[key] = current[key];
}
return acc;
}, {});
return getSourceNodes(source)
.filter(node => {
return node.kind == ts.SyntaxKind.Decorator
&& (node as ts.Decorator).expression.kind == ts.SyntaxKind.CallExpression;
})
.map(node => (node as ts.Decorator).expression as ts.CallExpression)
.filter(expr => {
if (expr.expression.kind == ts.SyntaxKind.Identifier) {
const id = expr.expression as ts.Identifier;
return id.text == identifier && angularImports[id.text] === module;
} else if (expr.expression.kind == ts.SyntaxKind.PropertyAccessExpression) {
// This covers foo.NgModule when importing * as foo.
const paExpr = expr.expression as ts.PropertyAccessExpression;
// If the left expression is not an identifier, just give up at that point.
if (paExpr.expression.kind !== ts.SyntaxKind.Identifier) {
return false;
}
const id = paExpr.name.text;
const moduleId = (paExpr.expression as ts.Identifier).text;
return id === identifier && (angularImports[moduleId + '.'] === module);
}
return false;
})
.filter(expr => expr.arguments[0]
&& expr.arguments[0].kind == ts.SyntaxKind.ObjectLiteralExpression)
.map(expr => expr.arguments[0] as ts.ObjectLiteralExpression);
}
function findClassDeclarationParent(node: ts.Node): ts.ClassDeclaration|undefined {
if (ts.isClassDeclaration(node)) {
return node;
}
return node.parent && findClassDeclarationParent(node.parent);
}
/**
* Given a source file with @NgModule class(es), find the name of the first @NgModule class.
*
* @param source source file containing one or more @NgModule
* @returns the name of the first @NgModule, or `undefined` if none is found
*/
export function getFirstNgModuleName(source: ts.SourceFile): string|undefined {
// First, find the @NgModule decorators.
const ngModulesMetadata = getDecoratorMetadata(source, 'NgModule', '@angular/core');
if (ngModulesMetadata.length === 0) {
return undefined;
}
// Then walk parent pointers up the AST, looking for the ClassDeclaration parent of the NgModule
// metadata.
const moduleClass = findClassDeclarationParent(ngModulesMetadata[0]);
if (!moduleClass || !moduleClass.name) {
return undefined;
}
// Get the class name of the module ClassDeclaration.
return moduleClass.name.text;
}
export function getMetadataField(
node: ts.ObjectLiteralExpression,
metadataField: string,
): ts.ObjectLiteralElement[] {
return node.properties
.filter(ts.isPropertyAssignment)
// Filter out every fields that's not "metadataField". Also handles string literals
// (but not expressions).
.filter(({ name }) => {
return (ts.isIdentifier(name) || ts.isStringLiteral(name))
&& name.getText() === metadataField;
});
}
export function addSymbolToNgModuleMetadata(
source: ts.SourceFile,
ngModulePath: string,
metadataField: string,
symbolName: string,
importPath: string | null = null,
): Change[] {
const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core');
let node: any = nodes[0]; // tslint:disable-line:no-any
// Find the decorator declaration.
if (!node) {
return [];
}
// Get all the children property assignment of object literals.
const matchingProperties = getMetadataField(
node as ts.ObjectLiteralExpression,
metadataField,
);
// Get the last node of the array literal.
if (!matchingProperties) {
return [];
}
if (matchingProperties.length == 0) {
// We haven't found the field in the metadata declaration. Insert a new field.
const expr = node as ts.ObjectLiteralExpression;
let position: number;
let toInsert: string;
if (expr.properties.length == 0) {
position = expr.getEnd() - 1;
toInsert = ` ${metadataField}: [${symbolName}]\n`;
} else {
node = expr.properties[expr.properties.length - 1];
position = node.getEnd();
// Get the indentation of the last element, if any.
const text = node.getFullText(source);
const matches = text.match(/^\r?\n\s*/);
if (matches && matches.length > 0) {
toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`;
} else {
toInsert = `, ${metadataField}: [${symbolName}]`;
}
}
if (importPath !== null) {
return [
new InsertChange(ngModulePath, position, toInsert),
insertImport(source, ngModulePath, symbolName.replace(/\..*$/, ''), importPath),
];
} else {
return [new InsertChange(ngModulePath, position, toInsert)];
}
}
const assignment = matchingProperties[0] as ts.PropertyAssignment;
// If it's not an array, nothing we can do really.
if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {
return [];
}
const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression;
if (arrLiteral.elements.length == 0) {
// Forward the property.
node = arrLiteral;
} else {
node = arrLiteral.elements;
}
if (!node) {
// tslint:disable-next-line: no-console
console.error('No app module found. Please add your new class to your component.');
return [];
}
if (Array.isArray(node)) {
const nodeArray = node as {} as Array<ts.Node>;
const symbolsArray = nodeArray.map(node => node.getText());
if (symbolsArray.includes(symbolName)) {
return [];
}
node = node[node.length - 1];
}
let toInsert: string;
let position = node.getEnd();
if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) {
// We haven't found the field in the metadata declaration. Insert a new
// field.
const expr = node as ts.ObjectLiteralExpression;
if (expr.properties.length == 0) {
position = expr.getEnd() - 1;
toInsert = ` ${symbolName}\n`;
} else {
// Get the indentation of the last element, if any.
const text = node.getFullText(source);
if (text.match(/^\r?\r?\n/)) {
toInsert = `,${text.match(/^\r?\n\s*/)[0]}${symbolName}`;
} else {
toInsert = `, ${symbolName}`;
}
}
} else if (node.kind == ts.SyntaxKind.ArrayLiteralExpression) {
// We found the field but it's empty. Insert it just before the `]`.
position--;
toInsert = `${symbolName}`;
} else {
// Get the indentation of the last element, if any.
const text = node.getFullText(source);
if (text.match(/^\r?\n/)) {
toInsert = `,${text.match(/^\r?\n(\r?)\s*/)[0]}${symbolName}`;
} else {
toInsert = `, ${symbolName}`;
}
}
if (importPath !== null) {
return [
new InsertChange(ngModulePath, position, toInsert),
insertImport(source, ngModulePath, symbolName.replace(/\..*$/, ''), importPath),
];
}
return [new InsertChange(ngModulePath, position, toInsert)];
}
/**
* Custom function to insert a declaration (component, pipe, directive)
* into NgModule declarations. It also imports the component.
*/
export function addDeclarationToModule(source: ts.SourceFile,
modulePath: string, classifiedName: string,
importPath: string): Change[] {
return addSymbolToNgModuleMetadata(
source, modulePath, 'declarations', classifiedName, importPath);
}
/**
* Custom function to insert an NgModule into NgModule imports. It also imports the module.
*/
export function addImportToModule(source: ts.SourceFile,
modulePath: string, classifiedName: string,
importPath: string): Change[] {
return addSymbolToNgModuleMetadata(source, modulePath, 'imports', classifiedName, importPath);
}
/**
* Custom function to insert a provider into NgModule. It also imports it.
*/
export function addProviderToModule(source: ts.SourceFile,
modulePath: string, classifiedName: string,
importPath: string): Change[] {
return addSymbolToNgModuleMetadata(source, modulePath, 'providers', classifiedName, importPath);
}
/**
* Custom function to insert an export into NgModule. It also imports it.
*/
export function addExportToModule(source: ts.SourceFile,
modulePath: string, classifiedName: string,
importPath: string): Change[] {
return addSymbolToNgModuleMetadata(source, modulePath, 'exports', classifiedName, importPath);
}
/**
* Custom function to insert an export into NgModule. It also imports it.
*/
export function addBootstrapToModule(source: ts.SourceFile,
modulePath: string, classifiedName: string,
importPath: string): Change[] {
return addSymbolToNgModuleMetadata(source, modulePath, 'bootstrap', classifiedName, importPath);
}
/**
* Custom function to insert an entryComponent into NgModule. It also imports it.
* @deprecated - Since version 9.0.0 with Ivy, entryComponents is no longer necessary.
*/
export function addEntryComponentToModule(source: ts.SourceFile,
modulePath: string, classifiedName: string,
importPath: string): Change[] {
return addSymbolToNgModuleMetadata(
source, modulePath,
'entryComponents', classifiedName, importPath,
);
}
/**
* Determine if an import already exists.
*/
export function isImported(source: ts.SourceFile,
classifiedName: string,
importPath: string): boolean {
const allNodes = getSourceNodes(source);
const matchingNodes = allNodes
.filter(ts.isImportDeclaration)
.filter(
(imp) => ts.isStringLiteral(imp.moduleSpecifier) && imp.moduleSpecifier.text === importPath,
)
.filter((imp) => {
if (!imp.importClause) {
return false;
}
const nodes = findNodes(imp.importClause, ts.isImportSpecifier).filter(
(n) => n.getText() === classifiedName,
);
return nodes.length > 0;
});
return matchingNodes.length > 0;
}
/**
* This function returns the name of the environment export
* whether this export is aliased or not. If the environment file
* is not imported, then it will return `null`.
*/
export function getEnvironmentExportName(source: ts.SourceFile): string | null {
// Initial value is `null` as we don't know yet if the user
// has imported `environment` into the root module or not.
let environmentExportName: string | null = null;
const allNodes = getSourceNodes(source);
allNodes
.filter(ts.isImportDeclaration)
.filter(
(declaration) =>
declaration.moduleSpecifier.kind === ts.SyntaxKind.StringLiteral &&
declaration.importClause !== undefined,
)
.map((declaration) =>
// If `importClause` property is defined then the first
// child will be `NamedImports` object (or `namedBindings`).
(declaration.importClause as ts.ImportClause).getChildAt(0),
)
// Find those `NamedImports` object that contains `environment` keyword
// in its text. E.g. `{ environment as env }`.
.filter(ts.isNamedImports)
.filter((namedImports) => namedImports.getText().includes('environment'))
.forEach((namedImports) => {
for (const specifier of namedImports.elements) {
// `propertyName` is defined if the specifier
// has an aliased import.
const name = specifier.propertyName || specifier.name;
// Find specifier that contains `environment` keyword in its text.
// Whether it's `environment` or `environment as env`.
if (name.text.includes('environment')) {
environmentExportName = specifier.name.text;
}
}
});
return environmentExportName;
}
/**
* Returns the RouterModule declaration from NgModule metadata, if any.
*/
export function getRouterModuleDeclaration(source: ts.SourceFile): ts.Expression | undefined {
const result = getDecoratorMetadata(source, 'NgModule', '@angular/core') as ts.Node[];
const node = result[0] as ts.ObjectLiteralExpression;
const matchingProperties = getMetadataField(node, 'imports');
if (!matchingProperties) {
return;
}
const assignment = matchingProperties[0] as ts.PropertyAssignment;
if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) {
return;
}
const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression;
return arrLiteral.elements
.filter(el => el.kind === ts.SyntaxKind.CallExpression)
.find(el => (el as ts.Identifier).getText().startsWith('RouterModule'));
}
/**
* Adds a new route declaration to a router module (i.e. has a RouterModule declaration)
*/
export function addRouteDeclarationToModule(
source: ts.SourceFile,
fileToAdd: string,
routeLiteral: string,
): Change {
const routerModuleExpr = getRouterModuleDeclaration(source);
if (!routerModuleExpr) {
throw new Error(`Couldn't find a route declaration in ${fileToAdd}.`);
}
const scopeConfigMethodArgs = (routerModuleExpr as ts.CallExpression).arguments;
if (!scopeConfigMethodArgs.length) {
const { line } = source.getLineAndCharacterOfPosition(routerModuleExpr.getStart());
throw new Error(
`The router module method doesn't have arguments ` +
`at line ${line} in ${fileToAdd}`,
);
}
let routesArr: ts.ArrayLiteralExpression | undefined;
const routesArg = scopeConfigMethodArgs[0];
// Check if the route declarations array is
// an inlined argument of RouterModule or a standalone variable
if (ts.isArrayLiteralExpression(routesArg)) {
routesArr = routesArg;
} else {
const routesVarName = routesArg.getText();
let routesVar;
if (routesArg.kind === ts.SyntaxKind.Identifier) {
routesVar = source.statements
.filter(ts.isVariableStatement)
.find((v) => {
return v.declarationList.declarations[0].name.getText() === routesVarName;
});
}
if (!routesVar) {
const { line } = source.getLineAndCharacterOfPosition(routesArg.getStart());
throw new Error(
`No route declaration array was found that corresponds ` +
`to router module at line ${line} in ${fileToAdd}`,
);
}
routesArr = findNodes(routesVar, ts.SyntaxKind.ArrayLiteralExpression, 1)[0] as ts.ArrayLiteralExpression;
}
const occurrencesCount = routesArr.elements.length;
const text = routesArr.getFullText(source);
let route: string = routeLiteral;
let insertPos = routesArr.elements.pos;
if (occurrencesCount > 0) {
const lastRouteLiteral = [...routesArr.elements].pop() as ts.Expression;
const lastRouteIsWildcard = ts.isObjectLiteralExpression(lastRouteLiteral)
&& lastRouteLiteral
.properties
.some(n => (
ts.isPropertyAssignment(n)
&& ts.isIdentifier(n.name)
&& n.name.text === 'path'
&& ts.isStringLiteral(n.initializer)
&& n.initializer.text === '**'
));
const indentation = text.match(/\r?\n(\r?)\s*/) || [];
const routeText = `${indentation[0] || ' '}${routeLiteral}`;
// Add the new route before the wildcard route
// otherwise we'll always redirect to the wildcard route
if (lastRouteIsWildcard) {
insertPos = lastRouteLiteral.pos;
route = `${routeText},`;
} else {
insertPos = lastRouteLiteral.end;
route = `,${routeText}`;
}
}
return new InsertChange(fileToAdd, insertPos, route);
}

127
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/change.ts

@ -0,0 +1,127 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export interface Host {
write(path: string, content: string): Promise<void>;
read(path: string): Promise<string>;
}
export interface Change {
apply(host: Host): Promise<void>;
// The file this change should be applied to. Some changes might not apply to
// a file (maybe the config).
readonly path: string | null;
// The order this change should be applied. Normally the position inside the file.
// Changes are applied from the bottom of a file to the top.
readonly order: number;
// The description of this change. This will be outputted in a dry or verbose run.
readonly description: string;
}
/**
* An operation that does nothing.
*/
export class NoopChange implements Change {
description = 'No operation.';
order = Infinity;
path = null;
apply() { return Promise.resolve(); }
}
/**
* Will add text to the source code.
*/
export class InsertChange implements Change {
order: number;
description: string;
constructor(public path: string, public pos: number, public toAdd: string) {
if (pos < 0) {
throw new Error('Negative positions are invalid');
}
this.description = `Inserted ${toAdd} into position ${pos} of ${path}`;
this.order = pos;
}
/**
* This method does not insert spaces if there is none in the original string.
*/
apply(host: Host) {
return host.read(this.path).then(content => {
const prefix = content.substring(0, this.pos);
const suffix = content.substring(this.pos);
return host.write(this.path, `${prefix}${this.toAdd}${suffix}`);
});
}
}
/**
* Will remove text from the source code.
*/
export class RemoveChange implements Change {
order: number;
description: string;
constructor(public path: string, private pos: number, private toRemove: string) {
if (pos < 0) {
throw new Error('Negative positions are invalid');
}
this.description = `Removed ${toRemove} into position ${pos} of ${path}`;
this.order = pos;
}
apply(host: Host): Promise<void> {
return host.read(this.path).then(content => {
const prefix = content.substring(0, this.pos);
const suffix = content.substring(this.pos + this.toRemove.length);
// TODO: throw error if toRemove doesn't match removed string.
return host.write(this.path, `${prefix}${suffix}`);
});
}
}
/**
* Will replace text from the source code.
*/
export class ReplaceChange implements Change {
order: number;
description: string;
constructor(public path: string, private pos: number, private oldText: string,
private newText: string) {
if (pos < 0) {
throw new Error('Negative positions are invalid');
}
this.description = `Replaced ${oldText} into position ${pos} of ${path} with ${newText}`;
this.order = pos;
}
apply(host: Host): Promise<void> {
return host.read(this.path).then(content => {
const prefix = content.substring(0, this.pos);
const suffix = content.substring(this.pos + this.oldText.length);
const text = content.substring(this.pos, this.pos + this.oldText.length);
if (text !== this.oldText) {
return Promise.reject(new Error(`Invalid replace: "${text}" != "${this.oldText}".`));
}
// TODO: throw error if oldText doesn't match removed string.
return host.write(this.path, `${prefix}${this.newText}${suffix}`);
});
}
}

532
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/config.ts

@ -0,0 +1,532 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { JsonParseMode, parseJson } from '@angular-devkit/core';
import { Rule, SchematicContext, SchematicsException, Tree } from '@angular-devkit/schematics';
import { ProjectType, WorkspaceProject, WorkspaceSchema } from './workspace-models';
// The interfaces below are generated from the Angular CLI configuration schema
// https://github.com/angular/angular-cli/blob/master/packages/@angular/cli/lib/config/schema.json
export interface AppConfig {
/**
* Name of the app.
*/
name?: string;
/**
* Directory where app files are placed.
*/
appRoot?: string;
/**
* The root directory of the app.
*/
root?: string;
/**
* The output directory for build results.
*/
outDir?: string;
/**
* List of application assets.
*/
assets?: (string | {
/**
* The pattern to match.
*/
glob?: string;
/**
* The dir to search within.
*/
input?: string;
/**
* The output path (relative to the outDir).
*/
output?: string;
})[];
/**
* URL where files will be deployed.
*/
deployUrl?: string;
/**
* Base url for the application being built.
*/
baseHref?: string;
/**
* The runtime platform of the app.
*/
platform?: ('browser' | 'server');
/**
* The name of the start HTML file.
*/
index?: string;
/**
* The name of the main entry-point file.
*/
main?: string;
/**
* The name of the polyfills file.
*/
polyfills?: string;
/**
* The name of the test entry-point file.
*/
test?: string;
/**
* The name of the TypeScript configuration file.
*/
tsconfig?: string;
/**
* The name of the TypeScript configuration file for unit tests.
*/
testTsconfig?: string;
/**
* The prefix to apply to generated selectors.
*/
prefix?: string;
/**
* Experimental support for a service worker from @angular/service-worker.
*/
serviceWorker?: boolean;
/**
* Global styles to be included in the build.
*/
styles?: (string | {
input?: string;
[name: string]: any; // tslint:disable-line:no-any
})[];
/**
* Options to pass to style preprocessors
*/
stylePreprocessorOptions?: {
/**
* Paths to include. Paths will be resolved to project root.
*/
includePaths?: string[];
};
/**
* Global scripts to be included in the build.
*/
scripts?: (string | {
input: string;
[name: string]: any; // tslint:disable-line:no-any
})[];
/**
* Source file for environment config.
*/
environmentSource?: string;
/**
* Name and corresponding file for environment config.
*/
environments?: {
[name: string]: any; // tslint:disable-line:no-any
};
appShell?: {
app: string;
route: string;
};
budgets?: {
/**
* The type of budget
*/
type?: ('bundle' | 'initial' | 'allScript' | 'all' | 'anyScript' | 'any' | 'anyComponentStyle');
/**
* The name of the bundle
*/
name?: string;
/**
* The baseline size for comparison.
*/
baseline?: string;
/**
* The maximum threshold for warning relative to the baseline.
*/
maximumWarning?: string;
/**
* The maximum threshold for error relative to the baseline.
*/
maximumError?: string;
/**
* The minimum threshold for warning relative to the baseline.
*/
minimumWarning?: string;
/**
* The minimum threshold for error relative to the baseline.
*/
minimumError?: string;
/**
* The threshold for warning relative to the baseline (min & max).
*/
warning?: string;
/**
* The threshold for error relative to the baseline (min & max).
*/
error?: string;
}[];
}
export interface CliConfig {
$schema?: string;
/**
* The global configuration of the project.
*/
project?: {
/**
* The name of the project.
*/
name?: string;
/**
* Whether or not this project was ejected.
*/
ejected?: boolean;
};
/**
* Properties of the different applications in this project.
*/
apps?: AppConfig[];
/**
* Configuration for end-to-end tests.
*/
e2e?: {
protractor?: {
/**
* Path to the config file.
*/
config?: string;
};
};
/**
* Properties to be passed to TSLint.
*/
lint?: {
/**
* File glob(s) to lint.
*/
files?: (string | string[]);
/**
* Location of the tsconfig.json project file.
* Will also use as files to lint if 'files' property not present.
*/
project: string;
/**
* Location of the tslint.json configuration.
*/
tslintConfig?: string;
/**
* File glob(s) to ignore.
*/
exclude?: (string | string[]);
}[];
/**
* Configuration for unit tests.
*/
test?: {
karma?: {
/**
* Path to the karma config file.
*/
config?: string;
};
codeCoverage?: {
/**
* Globs to exclude from code coverage.
*/
exclude?: string[];
};
};
/**
* Specify the default values for generating.
*/
defaults?: {
/**
* The file extension to be used for style files.
*/
styleExt?: string;
/**
* How often to check for file updates.
*/
poll?: number;
/**
* Use lint to fix files after generation
*/
lintFix?: boolean;
/**
* Options for generating a class.
*/
class?: {
/**
* Specifies if a spec file is generated.
*/
spec?: boolean;
};
/**
* Options for generating a component.
*/
component?: {
/**
* Flag to indicate if a directory is created.
*/
flat?: boolean;
/**
* Specifies if a spec file is generated.
*/
spec?: boolean;
/**
* Specifies if the style will be in the ts file.
*/
inlineStyle?: boolean;
/**
* Specifies if the template will be in the ts file.
*/
inlineTemplate?: boolean;
/**
* Specifies the view encapsulation strategy.
*/
viewEncapsulation?: ('Emulated' | 'Native' | 'None');
/**
* Specifies the change detection strategy.
*/
changeDetection?: ('Default' | 'OnPush');
};
/**
* Options for generating a directive.
*/
directive?: {
/**
* Flag to indicate if a directory is created.
*/
flat?: boolean;
/**
* Specifies if a spec file is generated.
*/
spec?: boolean;
};
/**
* Options for generating a guard.
*/
guard?: {
/**
* Flag to indicate if a directory is created.
*/
flat?: boolean;
/**
* Specifies if a spec file is generated.
*/
spec?: boolean;
};
/**
* Options for generating an interface.
*/
interface?: {
/**
* Prefix to apply to interface names. (i.e. I)
*/
prefix?: string;
};
/**
* Options for generating a module.
*/
module?: {
/**
* Flag to indicate if a directory is created.
*/
flat?: boolean;
/**
* Specifies if a spec file is generated.
*/
spec?: boolean;
};
/**
* Options for generating a pipe.
*/
pipe?: {
/**
* Flag to indicate if a directory is created.
*/
flat?: boolean;
/**
* Specifies if a spec file is generated.
*/
spec?: boolean;
};
/**
* Options for generating a service.
*/
service?: {
/**
* Flag to indicate if a directory is created.
*/
flat?: boolean;
/**
* Specifies if a spec file is generated.
*/
spec?: boolean;
};
/**
* Properties to be passed to the build command.
*/
build?: {
/**
* Output sourcemaps.
*/
sourcemaps?: boolean;
/**
* Base url for the application being built.
*/
baseHref?: string;
/**
* The ssl key used by the server.
*/
progress?: boolean;
/**
* Enable and define the file watching poll time period (milliseconds).
*/
poll?: number;
/**
* Delete output path before build.
*/
deleteOutputPath?: boolean;
/**
* Do not use the real path when resolving modules.
*/
preserveSymlinks?: boolean;
/**
* Show circular dependency warnings on builds.
*/
showCircularDependencies?: boolean;
/**
* Use a separate bundle containing code used across multiple bundles.
*/
commonChunk?: boolean;
/**
* Use file name for lazy loaded chunks.
*/
namedChunks?: boolean;
};
/**
* Properties to be passed to the serve command.
*/
serve?: {
/**
* The port the application will be served on.
*/
port?: number;
/**
* The host the application will be served on.
*/
host?: string;
/**
* Enables ssl for the application.
*/
ssl?: boolean;
/**
* The ssl key used by the server.
*/
sslKey?: string;
/**
* The ssl certificate used by the server.
*/
sslCert?: string;
/**
* Proxy configuration file.
*/
proxyConfig?: string;
};
/**
* Properties about schematics.
*/
schematics?: {
/**
* The schematics collection to use.
*/
collection?: string;
/**
* The new app schematic.
*/
newApp?: string;
};
};
/**
* Specify which package manager tool to use.
*/
packageManager?: ('npm' | 'cnpm' | 'yarn' | 'default');
/**
* Allow people to disable console warnings.
*/
warnings?: {
versionMismatch?: boolean;
};
}
export function getWorkspacePath(host: Tree): string {
const possibleFiles = [ '/angular.json', '/.angular.json' ];
const path = possibleFiles.filter(path => host.exists(path))[0];
return path;
}
export function getWorkspaceSchema(host: Tree): WorkspaceSchema {
const path = getWorkspacePath(host);
const configBuffer = host.read(path);
if (configBuffer === null) {
throw new SchematicsException(`Could not find (${path})`);
}
const content = configBuffer.toString();
return parseJson(content, JsonParseMode.Loose) as {} as WorkspaceSchema;
}
export function addProjectToWorkspace<TProjectType extends ProjectType = ProjectType.Application>(
workspace: WorkspaceSchema,
name: string,
project: WorkspaceProject<TProjectType>,
): Rule {
return (_host: Tree, _context: SchematicContext) => {
if (workspace.projects[name]) {
throw new Error(`Project '${name}' already exists in workspace.`);
}
// Add project to workspace.
workspace.projects[name] = project;
if (!workspace.defaultProject && Object.keys(workspace.projects).length === 1) {
// Make the new project the default one.
workspace.defaultProject = name;
}
return updateWorkspaceSchema(workspace);
};
}
export function updateWorkspaceSchema(workspace: WorkspaceSchema): Rule {
return (host: Tree, _context: SchematicContext) => {
host.overwrite(getWorkspacePath(host), JSON.stringify(workspace, null, 2));
};
}
export const configPath = '/.angular-cli.json';
export function getConfig(host: Tree): CliConfig {
const configBuffer = host.read(configPath);
if (configBuffer === null) {
throw new SchematicsException('Could not find .angular-cli.json');
}
const config = parseJson(configBuffer.toString(), JsonParseMode.Loose) as {} as CliConfig;
return config;
}
export function getAppFromConfig(config: CliConfig, appIndexOrName: string): AppConfig | null {
if (!config.apps) {
return null;
}
if (parseInt(appIndexOrName) >= 0) {
return config.apps[parseInt(appIndexOrName)];
}
return config.apps.filter((app) => app.name === appIndexOrName)[0];
}

76
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/dependencies.ts

@ -0,0 +1,76 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { Tree } from '@angular-devkit/schematics';
import { JSONFile } from './json-file';
const PKG_JSON_PATH = '/package.json';
export enum NodeDependencyType {
Default = 'dependencies',
Dev = 'devDependencies',
Peer = 'peerDependencies',
Optional = 'optionalDependencies',
}
export interface NodeDependency {
type: NodeDependencyType;
name: string;
version: string;
overwrite?: boolean;
}
const ALL_DEPENDENCY_TYPE = [
NodeDependencyType.Default,
NodeDependencyType.Dev,
NodeDependencyType.Optional,
NodeDependencyType.Peer,
];
export function addPackageJsonDependency(tree: Tree, dependency: NodeDependency, pkgJsonPath = PKG_JSON_PATH): void {
const json = new JSONFile(tree, pkgJsonPath);
if (json.error) {
throw json.error;
}
const { overwrite, type, name, version } = dependency;
const path = [type, name];
if (overwrite || !json.get(path)) {
json.modify(path, version);
}
}
export function removePackageJsonDependency(tree: Tree, name: string, pkgJsonPath = PKG_JSON_PATH): void {
const json = new JSONFile(tree, pkgJsonPath);
if (json.error) {
throw json.error;
}
for (const depType of ALL_DEPENDENCY_TYPE) {
json.remove([depType, name]);
}
}
export function getPackageJsonDependency(tree: Tree, name: string, pkgJsonPath = PKG_JSON_PATH): NodeDependency | null {
const json = new JSONFile(tree, pkgJsonPath);
if (json.error) {
throw json.error;
}
for (const depType of ALL_DEPENDENCY_TYPE) {
const version = json.get([depType, name]);
if (typeof version === 'string') {
return {
type: depType,
name: name,
version,
};
}
}
return null;
}

151
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/find-module.ts

@ -0,0 +1,151 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {
dirname,
join,
normalize, NormalizedRoot,
Path,
relative
} from '@angular-devkit/core';
import { DirEntry, Tree } from '@angular-devkit/schematics';
export interface ModuleOptions {
project?: string; // added this
module?: string;
name: string;
flat?: boolean;
path?: string;
route?: string; // added this
selector?: string; // added this
skipImport?: boolean;
moduleExt?: string;
routingModuleExt?: string;
}
export const MODULE_EXT = '.module.ts';
export const ROUTING_MODULE_EXT = '-routing.module.ts';
/**
* Find the module referred by a set of options passed to the schematics.
*/
export function findModuleFromOptions(host: Tree, options: ModuleOptions): Path | undefined {
if (options.hasOwnProperty('skipImport') && options.skipImport) {
return undefined;
}
const moduleExt = options.moduleExt || MODULE_EXT;
const routingModuleExt = options.routingModuleExt || ROUTING_MODULE_EXT;
if (!options.module) {
const pathToCheck = (options.path || '') + '/' + options.name;
return normalize(findModule(host, pathToCheck, moduleExt, routingModuleExt));
} else {
const modulePath = normalize(`/${options.path}/${options.module}`);
const componentPath = normalize(`/${options.path}/${options.name}`);
const moduleBaseName = normalize(modulePath).split('/').pop();
const candidateSet = new Set<Path>([
normalize(options.path || '/'),
]);
for (let dir = modulePath; dir != NormalizedRoot; dir = dirname(dir)) {
candidateSet.add(dir);
}
for (let dir = componentPath; dir != NormalizedRoot; dir = dirname(dir)) {
candidateSet.add(dir);
}
const candidatesDirs = [...candidateSet].sort((a, b) => b.length - a.length);
for (const c of candidatesDirs) {
const candidateFiles = [
'',
`${moduleBaseName}.ts`,
`${moduleBaseName}${moduleExt}`,
].map(x => join(c, x));
for (const sc of candidateFiles) {
if (host.exists(sc)) {
return normalize(sc);
}
}
}
throw new Error(
`Specified module '${options.module}' does not exist.\n`
+ `Looked in the following directories:\n ${candidatesDirs.join('\n ')}`,
);
}
}
/**
* Function to find the "closest" module to a generated file's path.
*/
export function findModule(host: Tree, generateDir: string,
moduleExt = MODULE_EXT, routingModuleExt = ROUTING_MODULE_EXT): Path {
let dir: DirEntry | null = host.getDir('/' + generateDir);
let foundRoutingModule = false;
while (dir) {
const allMatches = dir.subfiles.filter(p => p.endsWith(moduleExt));
const filteredMatches = allMatches.filter(p => !p.endsWith(routingModuleExt));
foundRoutingModule = foundRoutingModule || allMatches.length !== filteredMatches.length;
if (filteredMatches.length == 1) {
return join(dir.path, filteredMatches[0]);
} else if (filteredMatches.length > 1) {
throw new Error(
'More than one module matches. Use the skip-import option to skip importing ' +
'the component into the closest module or use the module option to specify a module.');
}
dir = dir.parent;
}
const errorMsg = foundRoutingModule ? 'Could not find a non Routing NgModule.'
+ `\nModules with suffix '${routingModuleExt}' are strictly reserved for routing.`
+ '\nUse the skip-import option to skip importing in NgModule.'
: 'Could not find an NgModule. Use the skip-import option to skip importing in NgModule.';
throw new Error(errorMsg);
}
/**
* Build a relative path from one file path to another file path.
*/
export function buildRelativePath(from: string, to: string): string {
from = normalize(from);
to = normalize(to);
// Convert to arrays.
const fromParts = from.split('/');
const toParts = to.split('/');
// Remove file names (preserving destination)
fromParts.pop();
const toFileName = toParts.pop();
const relativePath = relative(normalize(fromParts.join('/') || '/'),
normalize(toParts.join('/') || '/'));
let pathPrefix = '';
// Set the path prefix for same dir or child dir, parent dir starts with `..`
if (!relativePath) {
pathPrefix = '.';
} else if (!relativePath.startsWith('.')) {
pathPrefix = `./`;
}
if (pathPrefix && !pathPrefix.endsWith('/')) {
pathPrefix += '/';
}
return pathPrefix + (relativePath ? relativePath + '/' : '') + toFileName;
}

17
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/index.ts

@ -0,0 +1,17 @@
export * from './ast-utils';
export * from './change';
export * from './config';
export * from './dependencies';
export * from './find-module';
export * from './json-file';
export * from './json-utils';
export * from './latest-versions';
export * from './lint-fix';
export * from './ng-ast-utils';
export * from './parse-name';
export * from './paths';
export * from './project-targets';
export * from './tsconfig';
export * from './validation';
export * from './workspace';
export * from './workspace-models';

82
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/json-file.ts

@ -0,0 +1,82 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { JsonValue } from '@angular-devkit/core';
import { Tree } from '@angular-devkit/schematics';
import { Node, applyEdits, findNodeAtLocation, getNodeValue, modify, parseTree } from 'jsonc-parser';
export type JSONPath = (string | number)[];
/** @internal */
export class JSONFile {
private content: string;
error: undefined | Error;
constructor(
private readonly host: Tree,
private readonly path: string,
) {
const buffer = this.host.read(this.path);
if (buffer) {
this.content = buffer.toString();
} else {
this.error = new Error(`Could not read ${path}.`);
}
}
private _jsonAst: Node | undefined;
private get JsonAst(): Node {
if (this._jsonAst) {
return this._jsonAst;
}
this._jsonAst = parseTree(this.content);
return this._jsonAst;
}
get(jsonPath: JSONPath): unknown {
if (jsonPath.length === 0) {
return getNodeValue(this.JsonAst);
}
const node = findNodeAtLocation(this.JsonAst, jsonPath);
return node === undefined ? undefined : getNodeValue(node);
}
modify(jsonPath: JSONPath, value: JsonValue | undefined, getInsertionIndex?: (properties: string[]) => number): void {
if (!getInsertionIndex) {
const property = jsonPath.slice(-1)[0];
getInsertionIndex = properties => [...properties, property].sort().findIndex(p => p === property);
}
const edits = modify(
this.content,
jsonPath,
value,
{
getInsertionIndex,
formattingOptions: {
insertSpaces: true,
tabSize: 2,
},
},
);
this.content = applyEdits(this.content, edits);
this.host.overwrite(this.path, this.content);
this._jsonAst = undefined;
}
remove(jsonPath: JSONPath): void {
if (this.get(jsonPath) !== undefined) {
this.modify(jsonPath, undefined);
}
}
}

231
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/json-utils.ts

@ -0,0 +1,231 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {
JsonAstArray,
JsonAstKeyValue,
JsonAstNode,
JsonAstObject,
JsonValue,
} from '@angular-devkit/core';
import { UpdateRecorder } from '@angular-devkit/schematics';
export function appendPropertyInAstObject(
recorder: UpdateRecorder,
node: JsonAstObject,
propertyName: string,
value: JsonValue,
indent: number,
) {
const indentStr = _buildIndent(indent);
let index = node.start.offset + 1;
if (node.properties.length > 0) {
// Insert comma.
const last = node.properties[node.properties.length - 1];
const { text, end } = last;
const commaIndex = text.endsWith('\n') ? end.offset - 1 : end.offset;
recorder.insertRight(commaIndex, ',');
index = end.offset;
}
const content = _stringifyContent(value, indentStr);
recorder.insertRight(
index,
(node.properties.length === 0 && indent ? '\n' : '')
+ ' '.repeat(indent)
+ `"${propertyName}":${indent ? ' ' : ''}${content}`
+ indentStr.slice(0, -indent),
);
}
export function insertPropertyInAstObjectInOrder(
recorder: UpdateRecorder,
node: JsonAstObject,
propertyName: string,
value: JsonValue,
indent: number,
) {
if (node.properties.length === 0) {
appendPropertyInAstObject(recorder, node, propertyName, value, indent);
return;
}
// Find insertion info.
let insertAfterProp: JsonAstKeyValue | null = null;
let prev: JsonAstKeyValue | null = null;
let isLastProp = false;
const last = node.properties[node.properties.length - 1];
for (const prop of node.properties) {
if (prop.key.value > propertyName) {
if (prev) {
insertAfterProp = prev;
}
break;
}
if (prop === last) {
isLastProp = true;
insertAfterProp = last;
}
prev = prop;
}
if (isLastProp) {
appendPropertyInAstObject(recorder, node, propertyName, value, indent);
return;
}
const indentStr = _buildIndent(indent);
const insertIndex = insertAfterProp === null
? node.start.offset + 1
: insertAfterProp.end.offset + 1;
const content = _stringifyContent(value, indentStr);
recorder.insertRight(
insertIndex,
indentStr
+ `"${propertyName}":${indent ? ' ' : ''}${content}`
+ ',',
);
}
export function removePropertyInAstObject(
recorder: UpdateRecorder,
node: JsonAstObject,
propertyName: string,
) {
// Find the property inside the object.
const propIdx = node.properties.findIndex(prop => prop.key.value === propertyName);
if (propIdx === -1) {
// There's nothing to remove.
return;
}
if (node.properties.length === 1) {
// This is a special case. Everything should be removed, including indentation.
recorder.remove(node.start.offset, node.end.offset - node.start.offset);
recorder.insertRight(node.start.offset, '{}');
return;
}
// The AST considers commas and indentation to be part of the preceding property.
// To get around messy comma and identation management, we can work over the range between
// two properties instead.
const previousProp = node.properties[propIdx - 1];
const targetProp = node.properties[propIdx];
const nextProp = node.properties[propIdx + 1];
let start, end;
if (previousProp) {
// Given the object below, and intending to remove the `m` property:
// "{\n \"a\": \"a\",\n \"m\": \"m\",\n \"z\": \"z\"\n}"
// ^---------------^
// Removing the range above results in:
// "{\n \"a\": \"a\",\n \"z\": \"z\"\n}"
start = previousProp.end;
end = targetProp.end;
} else {
// If there's no previousProp there is a nextProp, since we've specialcased the 1 length case.
// Given the object below, and intending to remove the `a` property:
// "{\n \"a\": \"a\",\n \"m\": \"m\",\n \"z\": \"z\"\n}"
// ^---------------^
// Removing the range above results in:
// "{\n \"m\": \"m\",\n \"z\": \"z\"\n}"
start = targetProp.start;
end = nextProp.start;
}
recorder.remove(start.offset, end.offset - start.offset);
if (!nextProp) {
recorder.insertRight(start.offset, '\n');
}
}
export function appendValueInAstArray(
recorder: UpdateRecorder,
node: JsonAstArray,
value: JsonValue,
indent = 4,
) {
let indentStr = _buildIndent(indent);
let index = node.start.offset + 1;
// tslint:disable-next-line: no-any
let newNodes: any[] | undefined;
if (node.elements.length > 0) {
// Insert comma.
const { end } = node.elements[node.elements.length - 1];
const isClosingOnSameLine = node.end.offset - end.offset === 1;
if (isClosingOnSameLine && indent) {
// Reformat the entire array
recorder.remove(node.start.offset, node.end.offset - node.start.offset);
newNodes = [
...node.elements.map(({ value }) => value),
value,
];
index = node.start.offset;
// In case we are generating the entire node we need to reduce the spacing as
// otherwise we'd end up having incorrect double spacing
indent = indent - 2;
indentStr = _buildIndent(indent);
} else {
recorder.insertRight(end.offset, ',');
index = end.offset;
}
}
recorder.insertRight(
index,
(newNodes ? '' : indentStr)
+ _stringifyContent(newNodes || value, indentStr)
+ (node.elements.length === 0 && indent ? indentStr.substr(0, -indent) + '\n' : ''),
);
}
export function findPropertyInAstObject(
node: JsonAstObject,
propertyName: string,
): JsonAstNode | null {
let maybeNode: JsonAstNode | null = null;
for (const property of node.properties) {
if (property.key.value == propertyName) {
maybeNode = property.value;
}
}
return maybeNode;
}
function _buildIndent(count: number): string {
return count ? '\n' + ' '.repeat(count) : '';
}
function _stringifyContent(value: JsonValue, indentStr: string): string {
// TODO: Add snapshot tests
// The 'space' value is 2, because we want to add 2 additional
// indents from the 'key' node.
// If we use the indent provided we will have double indents:
// "budgets": [
// {
// "type": "initial",
// "maximumWarning": "2mb",
// "maximumError": "5mb"
// },
// {
// "type": "anyComponentStyle",
// 'maximumWarning": "5kb"
// }
// ]
return JSON.stringify(value, null, 2).replace(/\n/g, indentStr);
}

26
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/latest-versions.ts

@ -0,0 +1,26 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export const latestVersions = {
// These versions should be kept up to date with latest Angular peer dependencies.
Angular: '~10.0.0-rc.0',
RxJs: '~6.6.0',
ZoneJs: '~0.10.2',
TypeScript: '~3.9.5',
TsLib: '^2.0.0',
// The versions below must be manually updated when making a new devkit release.
// For our e2e tests, these versions must match the latest tag present on the branch.
// During RC periods they will not match the latest RC until there's a new git tag, and
// should not be updated.
DevkitBuildAngular: '~0.1000.0-rc.0',
DevkitBuildNgPackagr: '~0.1000.0-rc.0',
DevkitBuildWebpack: '~0.1000.0-rc.0',
ngPackagr: '^10.0.0',
};

51
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/lint-fix.ts

@ -0,0 +1,51 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {
DirEntry,
Rule,
SchematicContext,
SchematicsException,
Tree,
} from '@angular-devkit/schematics';
import { TslintFixTask } from '@angular-devkit/schematics/tasks';
export function applyLintFix(path = '/'): Rule {
return (tree: Tree, context: SchematicContext) => {
// Find the closest tslint.json or tslint.yaml
let dir: DirEntry | null = tree.getDir(path.substr(0, path.lastIndexOf('/')));
do {
if ((dir.subfiles as string[]).some(f => f === 'tslint.json' || f === 'tslint.yaml')) {
break;
}
dir = dir.parent;
} while (dir !== null);
if (dir === null) {
throw new SchematicsException(
'Asked to run lint fixes, but could not find a tslint.json or tslint.yaml config file.');
}
// Only include files that have been touched.
const files = tree.actions.reduce((acc: Set<string>, action) => {
const path = action.path.substr(1); // Remove the starting '/'.
if (path.endsWith('.ts') && dir && action.path.startsWith(dir.path)) {
acc.add(path);
}
return acc;
}, new Set<string>());
context.addTask(new TslintFixTask({
ignoreErrors: true,
tsConfigPath: 'tsconfig.json',
files: [...files],
}));
};
}

87
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/ng-ast-utils.ts

@ -0,0 +1,87 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { normalize } from '@angular-devkit/core';
import { SchematicsException, Tree } from '@angular-devkit/schematics';
import { dirname } from 'path';
import * as ts from 'typescript';
import { findNode, getSourceNodes } from './ast-utils';
export function findBootstrapModuleCall(host: Tree, mainPath: string): ts.CallExpression | null {
const mainBuffer = host.read(mainPath);
if (!mainBuffer) {
throw new SchematicsException(`Main file (${mainPath}) not found`);
}
const mainText = mainBuffer.toString('utf-8');
const source = ts.createSourceFile(mainPath, mainText, ts.ScriptTarget.Latest, true);
const allNodes = getSourceNodes(source);
let bootstrapCall: ts.CallExpression | null = null;
for (const node of allNodes) {
let bootstrapCallNode: ts.Node | null = null;
bootstrapCallNode = findNode(node, ts.SyntaxKind.Identifier, 'bootstrapModule');
// Walk up the parent until CallExpression is found.
while (
bootstrapCallNode &&
bootstrapCallNode.parent &&
bootstrapCallNode.parent.kind !== ts.SyntaxKind.CallExpression
) {
bootstrapCallNode = bootstrapCallNode.parent;
}
if (
bootstrapCallNode !== null &&
bootstrapCallNode.parent !== undefined &&
bootstrapCallNode.parent.kind === ts.SyntaxKind.CallExpression
) {
bootstrapCall = bootstrapCallNode.parent as ts.CallExpression;
break;
}
}
return bootstrapCall;
}
export function findBootstrapModulePath(host: Tree, mainPath: string): string {
const bootstrapCall = findBootstrapModuleCall(host, mainPath);
if (!bootstrapCall) {
throw new SchematicsException('Bootstrap call not found');
}
const bootstrapModule = bootstrapCall.arguments[0];
const mainBuffer = host.read(mainPath);
if (!mainBuffer) {
throw new SchematicsException(`Client app main file (${mainPath}) not found`);
}
const mainText = mainBuffer.toString('utf-8');
const source = ts.createSourceFile(mainPath, mainText, ts.ScriptTarget.Latest, true);
const allNodes = getSourceNodes(source);
const bootstrapModuleRelativePath = allNodes
.filter(node => node.kind === ts.SyntaxKind.ImportDeclaration)
.filter(imp => {
return findNode(imp, ts.SyntaxKind.Identifier, bootstrapModule.getText());
})
.map((imp: ts.ImportDeclaration) => {
const modulePathStringLiteral = imp.moduleSpecifier as ts.StringLiteral;
return modulePathStringLiteral.text;
})[0];
return bootstrapModuleRelativePath;
}
export function getAppModulePath(host: Tree, mainPath: string): string {
const moduleRelativePath = findBootstrapModulePath(host, mainPath);
const mainDir = dirname(mainPath);
const modulePath = normalize(`/${mainDir}/${moduleRelativePath}.ts`);
return modulePath;
}

25
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/parse-name.ts

@ -0,0 +1,25 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
// import { relative, Path } from "../../../angular_devkit/core/src/virtual-fs";
import { Path, basename, dirname, join, normalize } from '@angular-devkit/core';
export interface Location {
name: string;
path: Path;
}
export function parseName(path: string, name: string): Location {
const nameWithoutPath = basename(normalize(name));
const namePath = dirname(join(normalize(path), name) as Path);
return {
name: nameWithoutPath,
path: normalize('/' + namePath),
};
}

19
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/paths.ts

@ -0,0 +1,19 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { normalize, split } from '@angular-devkit/core';
export function relativePathToWorkspaceRoot(projectRoot: string | undefined): string {
const normalizedPath = split(normalize(projectRoot || ''));
if (normalizedPath.length === 0 || !normalizedPath[0]) {
return '.';
} else {
return normalizedPath.map(() => '..').join('/');
}
}

13
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/project-targets.ts

@ -0,0 +1,13 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { SchematicsException } from '@angular-devkit/schematics';
export function targetBuildNotFoundError(): SchematicsException {
return new SchematicsException(`Project target "build" not found.`);
}

70
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/tsconfig.ts

@ -0,0 +1,70 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { JsonParseMode, parseJsonAst } from '@angular-devkit/core';
import { Rule, SchematicsException, Tree } from '@angular-devkit/schematics';
import { appendValueInAstArray, findPropertyInAstObject } from './json-utils';
const SOLUTION_TSCONFIG_PATH = 'tsconfig.json';
/**
* Add project references in "Solution Style" tsconfig.
*/
export function addTsConfigProjectReferences(paths: string[]): Rule {
return (host, context) => {
const logger = context.logger;
// We need to read after each write to avoid missing `,` when appending multiple items.
for (const path of paths) {
const source = host.read(SOLUTION_TSCONFIG_PATH);
if (!source) {
// Solution tsconfig doesn't exist.
logger.warn(`Cannot add reference '${path}' in '${SOLUTION_TSCONFIG_PATH}'. File doesn't exists.`);
return;
}
const jsonAst = parseJsonAst(source.toString(), JsonParseMode.Loose);
if (jsonAst?.kind !== 'object') {
// Invalid JSON
throw new SchematicsException(`Invalid JSON AST Object '${SOLUTION_TSCONFIG_PATH}'.`);
}
// Solutions style tsconfig can contain 2 properties:
// - 'files' with a value of empty array
// - 'references'
const filesAst = findPropertyInAstObject(jsonAst, 'files');
const referencesAst = findPropertyInAstObject(jsonAst, 'references');
if (
filesAst?.kind !== 'array' ||
filesAst.elements.length !== 0 ||
referencesAst?.kind !== 'array'
) {
logger.warn(`Cannot add reference '${path}' in '${SOLUTION_TSCONFIG_PATH}'. It appears to be an invalid solution style tsconfig.`);
return;
}
// Append new paths
const recorder = host.beginUpdate(SOLUTION_TSCONFIG_PATH);
appendValueInAstArray(recorder, referencesAst, { 'path': `./${path}` }, 4);
host.commitUpdate(recorder);
}
};
}
/**
* Throws an exception when the base tsconfig doesn't exists.
*/
export function verifyBaseTsConfigExists(host: Tree): void {
if (host.exists('tsconfig.base.json')) {
return;
}
throw new SchematicsException(`Cannot find base TypeScript configuration file 'tsconfig.base.json'.`);
}

77
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/validation.ts

@ -0,0 +1,77 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { tags } from '@angular-devkit/core';
import { SchematicsException } from '@angular-devkit/schematics';
export function validateName(name: string): void {
if (name && /^\d/.test(name)) {
throw new SchematicsException(tags.oneLine`name (${name})
can not start with a digit.`);
}
}
// Must start with a letter, and must contain only alphanumeric characters or dashes.
// When adding a dash the segment after the dash must also start with a letter.
export const htmlSelectorRe = /^[a-zA-Z][.0-9a-zA-Z]*(:?-[a-zA-Z][.0-9a-zA-Z]*)*$/;
export function validateHtmlSelector(selector: string): void {
if (selector && !htmlSelectorRe.test(selector)) {
throw new SchematicsException(tags.oneLine`Selector (${selector})
is invalid.`);
}
}
export function validateProjectName(projectName: string) {
const errorIndex = getRegExpFailPosition(projectName);
const unsupportedProjectNames: string[] = [];
const packageNameRegex = /^(?:@[a-zA-Z0-9_-]+\/)?[a-zA-Z0-9_-]+$/;
if (errorIndex !== null) {
const firstMessage = tags.oneLine`
Project name "${projectName}" is not valid. New project names must
start with a letter, and must contain only alphanumeric characters or dashes.
When adding a dash the segment after the dash must also start with a letter.
`;
const msg = tags.stripIndent`
${firstMessage}
${projectName}
${Array(errorIndex + 1).join(' ') + '^'}
`;
throw new SchematicsException(msg);
} else if (unsupportedProjectNames.indexOf(projectName) !== -1) {
throw new SchematicsException(
`Project name ${JSON.stringify(projectName)} is not a supported name.`);
} else if (!packageNameRegex.test(projectName)) {
throw new SchematicsException(`Project name ${JSON.stringify(projectName)} is invalid.`);
}
}
function getRegExpFailPosition(str: string): number | null {
const isScope = /^@.*\/.*/.test(str);
if (isScope) {
// Remove starting @
str = str.replace(/^@/, '');
// Change / to - for validation
str = str.replace(/\//g, '-');
}
const parts = str.indexOf('-') >= 0 ? str.split('-') : [str];
const matched: string[] = [];
const projectNameRegexp = /^[a-zA-Z][.0-9a-zA-Z]*(-[.0-9a-zA-Z]*)*$/;
parts.forEach(part => {
if (part.match(projectNameRegexp)) {
matched.push(part);
}
});
const compare = matched.join('-');
return (str !== compare) ? compare.length : null;
}

179
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/workspace-models.ts

@ -0,0 +1,179 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export enum ProjectType {
Application = 'application',
Library = 'library',
}
export enum Builders {
AppShell = '@angular-devkit/build-angular:app-shell',
Server = '@angular-devkit/build-angular:server',
Browser = '@angular-devkit/build-angular:browser',
Karma = '@angular-devkit/build-angular:karma',
TsLint = '@angular-devkit/build-angular:tslint',
DeprecatedNgPackagr = '@angular-devkit/build-ng-packagr:build',
NgPackagr = '@angular-devkit/build-angular:ng-packagr',
DevServer = '@angular-devkit/build-angular:dev-server',
ExtractI18n = '@angular-devkit/build-angular:extract-i18n',
Protractor = '@angular-devkit/build-angular:protractor',
}
export interface FileReplacements {
replace: string;
with: string;
}
export interface BrowserBuilderBaseOptions {
main: string;
tsConfig: string;
fileReplacements?: FileReplacements[];
outputPath?: string;
index?: string;
polyfills: string;
assets?: (object | string)[];
styles?: (object | string)[];
scripts?: (object | string)[];
sourceMap?: boolean;
}
export type OutputHashing = 'all' | 'media' | 'none' | 'bundles';
export interface BrowserBuilderOptions extends BrowserBuilderBaseOptions {
serviceWorker?: boolean;
optimization?: boolean;
outputHashing?: OutputHashing;
resourcesOutputPath?: string;
extractCss?: boolean;
namedChunks?: boolean;
aot?: boolean;
extractLicenses?: boolean;
vendorChunk?: boolean;
buildOptimizer?: boolean;
ngswConfigPath?: string;
budgets?: {
type: string;
maximumWarning?: string;
maximumError?: string;
}[];
webWorkerTsConfig?: string;
}
export interface ServeBuilderOptions {
browserTarget: string;
}
export interface LibraryBuilderOptions {
tsConfig: string;
project: string;
}
export interface ServerBuilderOptions {
outputPath: string;
tsConfig: string;
main: string;
fileReplacements?: FileReplacements[];
optimization?: {
scripts?: boolean;
styles?: boolean;
};
sourceMap?:
| boolean
| {
scripts?: boolean;
styles?: boolean;
hidden?: boolean;
vendor?: boolean;
};
}
export interface AppShellBuilderOptions {
browserTarget: string;
serverTarget: string;
route: string;
}
export interface TestBuilderOptions extends Partial<BrowserBuilderBaseOptions> {
karmaConfig: string;
}
export interface LintBuilderOptions {
tsConfig: string[] | string;
exclude?: string[];
}
export interface ExtractI18nOptions {
browserTarget: string;
}
export interface E2EOptions {
protractorConfig: string;
devServerTarget: string;
}
export interface BuilderTarget<TBuilder extends Builders, TOptions> {
builder: TBuilder;
options: TOptions;
configurations?: {
production: Partial<TOptions>;
[key: string]: Partial<TOptions>;
};
}
export type LibraryBuilderTarget = BuilderTarget<Builders.NgPackagr, LibraryBuilderOptions>;
export type BrowserBuilderTarget = BuilderTarget<Builders.Browser, BrowserBuilderOptions>;
export type ServerBuilderTarget = BuilderTarget<Builders.Server, ServerBuilderOptions>;
export type AppShellBuilderTarget = BuilderTarget<Builders.AppShell, AppShellBuilderOptions>;
export type LintBuilderTarget = BuilderTarget<Builders.TsLint, LintBuilderOptions>;
export type TestBuilderTarget = BuilderTarget<Builders.Karma, TestBuilderOptions>;
export type ServeBuilderTarget = BuilderTarget<Builders.DevServer, ServeBuilderOptions>;
export type ExtractI18nBuilderTarget = BuilderTarget<Builders.ExtractI18n, ExtractI18nOptions>;
export type E2EBuilderTarget = BuilderTarget<Builders.Protractor, E2EOptions>;
export interface WorkspaceSchema {
version: 1;
defaultProject?: string;
cli?: { warnings?: Record<string, boolean> };
projects: {
[key: string]: WorkspaceProject<ProjectType.Application | ProjectType.Library>;
};
}
export interface WorkspaceProject<TProjectType extends ProjectType = ProjectType.Application> {
/**
* Project type.
*/
projectType: ProjectType;
root: string;
sourceRoot: string;
prefix: string;
cli?: { warnings?: Record<string, boolean> };
/**
* Tool options.
*/
architect?: WorkspaceTargets<TProjectType>;
/**
* Tool options.
*/
targets?: WorkspaceTargets<TProjectType>;
}
export interface WorkspaceTargets<TProjectType extends ProjectType = ProjectType.Application> {
build?: TProjectType extends ProjectType.Library ? LibraryBuilderTarget : BrowserBuilderTarget;
server?: ServerBuilderTarget;
lint?: LintBuilderTarget;
test?: TestBuilderTarget;
serve?: ServeBuilderTarget;
e2e?: E2EBuilderTarget;
'app-shell'?: AppShellBuilderTarget;
'extract-i18n'?: ExtractI18nBuilderTarget;
// TODO(hans): change this any to unknown when google3 supports TypeScript 3.0.
// tslint:disable-next-line:no-any
[key: string]: any;
}

91
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/angular/workspace.ts

@ -0,0 +1,91 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { virtualFs, workspaces } from '@angular-devkit/core';
import { Rule, Tree } from '@angular-devkit/schematics';
import { ProjectType } from './workspace-models';
function createHost(tree: Tree): workspaces.WorkspaceHost {
return {
async readFile(path: string): Promise<string> {
const data = tree.read(path);
if (!data) {
throw new Error('File not found.');
}
return virtualFs.fileBufferToString(data);
},
async writeFile(path: string, data: string): Promise<void> {
return tree.overwrite(path, data);
},
async isDirectory(path: string): Promise<boolean> {
// approximate a directory check
return !tree.exists(path) && tree.getDir(path).subfiles.length > 0;
},
async isFile(path: string): Promise<boolean> {
return tree.exists(path);
},
};
}
export function updateWorkspace(
updater: (workspace: workspaces.WorkspaceDefinition) => void | PromiseLike<void>,
): Rule;
export function updateWorkspace(
workspace: workspaces.WorkspaceDefinition,
): Rule;
export function updateWorkspace(
updaterOrWorkspace: workspaces.WorkspaceDefinition
| ((workspace: workspaces.WorkspaceDefinition) => void | PromiseLike<void>),
): Rule {
return async (tree: Tree) => {
const host = createHost(tree);
if (typeof updaterOrWorkspace === 'function') {
const { workspace } = await workspaces.readWorkspace('/', host);
const result = updaterOrWorkspace(workspace);
if (result !== undefined) {
await result;
}
await workspaces.writeWorkspace(workspace, host);
} else {
await workspaces.writeWorkspace(updaterOrWorkspace, host);
}
};
}
export async function getWorkspace(tree: Tree, path = '/') {
const host = createHost(tree);
const { workspace } = await workspaces.readWorkspace(path, host);
return workspace;
}
/**
* Build a default project path for generating.
* @param project The project which will have its default path generated.
*/
export function buildDefaultPath(project: workspaces.ProjectDefinition): string {
const root = project.sourceRoot ? `/${project.sourceRoot}/` : `/${project.root}/src/`;
const projectDirName = project.extensions['projectType'] === ProjectType.Application ? 'app' : 'lib';
return `${root}${projectDirName}`;
}
export async function createDefaultPath(tree: Tree, projectName: string): Promise<string> {
const workspace = await getWorkspace(tree);
const project = workspace.projects.get(projectName);
if (!project) {
throw new Error('Specified project does not exist.');
}
return buildDefaultPath(project);
}

6
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/api.ts

@ -0,0 +1,6 @@
import { chain, schematic } from '@angular-devkit/schematics';
import { GenerateProxySchema } from '../models';
export function createApisGenerator(schema: GenerateProxySchema, generated: string[]) {
return chain(generated.map(m => schematic('api', { ...schema, module: m })));
}

37
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/ast.ts

@ -0,0 +1,37 @@
import * as ts from 'typescript';
import { findNodes } from './angular/ast-utils';
export function findEnvironmentExpression(source: ts.SourceFile) {
const expressions = findNodes(source, ts.isObjectLiteralExpression);
return expressions.find(expr => expr.getText().includes('production'));
}
export function getAssignedPropertyFromObjectliteral(
expression: ts.ObjectLiteralExpression,
variableSelector: string[],
) {
const expressions = findNodes(expression, isBooleanStringOrNumberLiteral);
const literal = expressions.find(node =>
Boolean(
variableSelector.reduceRight(
(acc: ts.PropertyAssignment, key) =>
acc?.name?.getText() === key ? acc.parent.parent : undefined,
node.parent,
),
),
);
return literal ? literal.getText() : undefined;
}
export function isBooleanStringOrNumberLiteral(
node: ts.Node,
): node is ts.StringLiteral | ts.NumericLiteral | ts.BooleanLiteral {
return (
ts.isStringLiteral(node) ||
ts.isNumericLiteral(node) ||
node.kind === ts.SyntaxKind.TrueKeyword ||
node.kind === ts.SyntaxKind.FalseKeyword
);
}

99
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/barrel.ts

@ -0,0 +1,99 @@
import { strings } from '@angular-devkit/core';
import { Tree } from '@angular-devkit/schematics';
import { PROXY_PATH } from '../constants';
import { createFileSaver } from './file';
export function createProxyIndexGenerator(targetPath: string) {
return createBarrelsGenerator(targetPath + PROXY_PATH);
}
export function createBarrelsGenerator(rootPath: string) {
return (tree: Tree) => {
generateBarrelFromPath(tree, rootPath);
return tree;
};
}
export function generateBarrelFromPath(tree: Tree, indexPath: string) {
const saveFile = createFileSaver(tree);
const asterisk = collectAsteriskBarrel(tree, indexPath);
const named = collectNamedBarrel(tree, indexPath);
if (asterisk.exports.length + named.exports.length)
saveFile(indexPath + '/index.ts', generateBarrelContent(asterisk, named));
}
function generateBarrelContent(asterisk: AsteriskBarrel, named: NamedBarrel): string {
const namedImports = !named.imports.length
? ''
: named.imports.join(`
`) +
`
`;
const namedExports = !named.exports.length
? ''
: `export { ${named.exports.join(', ')} };
`;
const asteriskExports = !asterisk.exports.length
? ''
: asterisk.exports.join(`
`) +
`
`;
return namedImports + asteriskExports + namedExports;
}
function collectNamedBarrel(tree: Tree, indexPath: string) {
const dir = tree.getDir(indexPath);
const barrel = new NamedBarrel();
dir.subdirs.forEach(fragment => {
const subDirPath = indexPath + '/' + fragment;
const subDir = tree.getDir(subDirPath);
let hasFiles = false;
subDir.visit(() => (hasFiles = true));
if (!hasFiles) return;
const namespaceFragment = strings.classify(fragment);
barrel.imports.push(`import * as ${namespaceFragment} from './${fragment}';`);
barrel.exports.push(namespaceFragment);
generateBarrelFromPath(tree, subDirPath);
});
barrel.imports.sort();
barrel.exports.sort();
return barrel;
}
function collectAsteriskBarrel(tree: Tree, indexPath: string) {
const dir = tree.getDir(indexPath);
const barrel = new AsteriskBarrel();
dir.subfiles.forEach(fragment => {
if (!fragment.endsWith('.ts') || fragment === 'index.ts') return;
barrel.exports.push(`export * from './${fragment.replace(/\.ts$/, '')}';`);
});
barrel.exports.sort();
return barrel;
}
abstract class Barrel {
imports: string[] = [];
exports: string[] = [];
}
class AsteriskBarrel extends Barrel {
type = 'Asterisk' as const;
}
class NamedBarrel extends Barrel {
type = 'Named' as const;
}

35
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/common.ts

@ -0,0 +1,35 @@
import { SchematicsException, Tree } from '@angular-devkit/schematics';
import * as ts from 'typescript';
import { Exception } from '../enums';
export function interpolate(text: string, ...params: (string | number | boolean)[]) {
params.forEach((param, i) => {
const pattern = new RegExp('{\\s*' + i + '\\s*}');
text = text.replace(pattern, String(param));
});
return text;
}
export function isNullOrUndefined(value: any): value is null | undefined {
return value === null || value === undefined;
}
export function readFileInTree(tree: Tree, filePath: string): ts.SourceFile {
const buffer = tree.read(filePath);
if (isNullOrUndefined(buffer))
throw new SchematicsException(interpolate(Exception.FileNotFound, filePath));
const text = buffer.toString('utf-8');
return ts.createSourceFile(filePath, text, ts.ScriptTarget.Latest, true);
}
export function removeDefaultPlaceholders<T>(oldParams: T) {
const newParams: Record<string, any> = {};
Object.entries(oldParams).forEach(([key, value]) => {
newParams[key] = value === '__default' ? undefined : value;
});
return newParams as T;
}

47
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/enum.ts

@ -0,0 +1,47 @@
import { SchematicsException } from '@angular-devkit/schematics';
import { Exception } from '../enums';
import { Type } from '../models';
import { interpolate } from './common';
import { parseNamespace } from './namespace';
const shouldQuote = require('should-quote');
export interface EnumGeneratorParams {
targetPath: string;
solution: string;
types: Record<string, Type>;
serviceImports: Record<string, string[]>;
modelImports: Record<string, string[]>;
}
export function isEnumImport(path: string) {
return path.endsWith('.enum');
}
export function getEnumNamesFromImports(serviceImports: Record<string, string[]>) {
return Object.keys(serviceImports)
.filter(isEnumImport)
.reduce((acc: string[], path) => {
serviceImports[path].forEach(_import => acc.push(_import));
return acc;
}, []);
}
export function createImportRefToEnumMapper({ solution, types }: EnumGeneratorParams) {
return (ref: string) => {
const { enumNames, enumValues } = types[ref];
if (!enumNames || !enumValues)
throw new SchematicsException(interpolate(Exception.NoTypeDefinition, ref));
const namespace = parseNamespace(solution, ref);
const members = enumNames!.map((key, i) => ({
key: shouldQuote(key) ? `'${key}'` : key,
value: enumValues[i],
}));
return {
namespace,
name: ref.split('.').pop()!,
members,
};
};
}

8
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/file.ts

@ -0,0 +1,8 @@
import { Tree } from '@angular-devkit/schematics';
export function createFileSaver(tree: Tree) {
return (filePath: string, fileContent: string) =>
tree.exists(filePath)
? tree.overwrite(filePath, fileContent)
: tree.create(filePath, fileContent);
}

100
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/generics.ts

@ -0,0 +1,100 @@
import { Generic } from '../models';
export class GenericsCollector {
private _generics: Generic[] = [];
get generics() {
return this._generics;
}
apply = (value: string, index: number) => {
const generic = this.get(index);
if (generic) {
if (!generic.type) generic.setType(value);
return value + generic.default;
}
return value;
};
constructor(private getTypeIdentifier = (type: string) => type) {}
private createGeneric(type: string, ref: string, defaultValue: string) {
const _default = this.getTypeIdentifier(defaultValue);
const refs = [generateRefWithPlaceholders(ref)];
const generic = new Generic({ type, default: _default, refs });
return generic;
}
private register(index: number, generic: Generic) {
const existing = this.get(index);
if (existing) {
existing.setDefault(generic.default);
existing.setType(generic.type);
} else this.set(index, generic);
}
collect(generics: string[], genericArguments: string[]) {
generics.forEach((ref, i) => {
const generic = this.createGeneric(
genericArguments[i],
ref,
genericArguments.includes(ref) ? '' : ref,
);
this.register(i, generic);
});
}
get(index: number) {
return this.generics[index];
}
set(index: number, value: Generic) {
this.generics[index] = value;
}
reset() {
this._generics = [];
}
}
export function generateRefWithPlaceholders(sourceType: string) {
let { identifier, generics } = extractGenerics(sourceType);
identifier = identifier;
generics = generics.map((_, i) => `T${i}`);
return generics.length ? `${identifier}<${generics}>` : identifier;
}
export function extractSimpleGenerics(sourceType: string) {
const { identifier, generics } = extractGenerics(sourceType);
return {
identifier: getLastSegment(identifier),
generics: generics.map(getLastSegment),
};
}
export function extractGenerics(sourceType: string) {
const regex = /(?<identifier>[^<]+)(<(?<generics>.+)>)?/g;
const { identifier = '', generics = '' } = regex.exec(sourceType)?.groups ?? {};
return {
identifier,
generics: generics.split(/,\s*/).filter(Boolean),
};
}
function getLastSegment(str: string) {
return str.split('.').pop()!;
}
export function replacePlaceholdersWithGenerics(
type: string,
generics: string[],
genericsCollector: GenericsCollector,
) {
return generics
.map(genericsCollector.apply)
.reduce((acc, v, i) => acc.replace(new RegExp(`([<, ])T${i}([,>])`, 'g'), `$1${v}$2`), type);
}

11
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/import.ts

@ -0,0 +1,11 @@
import { Import } from '../models';
export function sortImports(imports: Import[]) {
imports.sort((a, b) =>
removeRelative(a) > removeRelative(b) ? 1 : a.keyword > b.keyword ? 1 : -1,
);
}
export function removeRelative(importDef: Import) {
return importDef.path.replace(/\.\.\//g, '');
}

19
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/index.ts

@ -0,0 +1,19 @@
export * from './angular';
export * from './api';
export * from './ast';
export * from './barrel';
export * from './common';
export * from './enum';
export * from './file';
export * from './generics';
export * from './import';
export * from './model';
export * from './namespace';
export * from './path';
export * from './rule';
export * from './service';
export * from './source';
export * from './text';
export * from './tree';
export * from './type';
export * from './workspace';

177
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/model.ts

@ -0,0 +1,177 @@
import { VOLO_REGEX } from '../constants';
import { Interface, Model, Property, PropertyDef, Type, TypeWithEnum } from '../models';
import {
extractGenerics,
generateRefWithPlaceholders,
GenericsCollector,
replacePlaceholdersWithGenerics,
} from './generics';
import { parseNamespace } from './namespace';
import { relativePathToModel } from './path';
import { camel } from './text';
import { parseGenerics } from './tree';
import {
createTypeParser,
createTypeSimplifier,
createTypesToImportsReducer,
extendsSelf,
removeTypeModifiers,
} from './type';
const shouldQuote = require('should-quote');
export interface ModelGeneratorParams {
targetPath: string;
solution: string;
types: Record<string, Type>;
serviceImports: Record<string, string[]>;
modelImports: Record<string, string[]>;
}
export function createImportRefsToModelReducer(params: ModelGeneratorParams) {
const reduceImportRefsToInterfaces = createImportRefToInterfaceReducerCreator(params);
const createRefToImportReducer = createRefToImportReducerCreator(params);
const { solution, types } = params;
return (models: Model[], importRefs: string[]) => {
const enums: string[] = [];
const interfaces = importRefs.reduce(reduceImportRefsToInterfaces, []);
sortInterfaces(interfaces);
interfaces.forEach(_interface => {
if (VOLO_REGEX.test(_interface.ref)) return;
if (types[_interface.ref]!.isEnum) {
if (!enums.includes(_interface.ref)) enums.push(_interface.ref);
return;
}
const index = models.findIndex(m => m.namespace === _interface.namespace);
if (index > -1) {
if (models[index].interfaces.some(i => i.identifier === _interface.identifier)) return;
models[index].interfaces.push(_interface);
} else {
const { namespace } = _interface;
models.push(
new Model({
interfaces: [_interface],
namespace,
path: relativePathToModel(namespace, namespace),
}),
);
}
});
models.forEach(model => {
const toBeImported: TypeWithEnum[] = [];
model.interfaces.forEach(_interface => {
const { baseType } = types[_interface.ref];
if (baseType && parseNamespace(solution, baseType) !== model.namespace)
toBeImported.push({
type: baseType.split('<')[0],
isEnum: false,
});
[..._interface.properties, ..._interface.generics].forEach(prop => {
prop.refs.forEach(ref => {
const propType = types[ref];
if (!propType) return;
if (propType.isEnum) toBeImported.push({ type: ref, isEnum: true });
else if (parseNamespace(solution, ref) !== model.namespace)
toBeImported.push({ type: ref, isEnum: false });
});
});
});
if (!toBeImported.length) return;
const reduceRefToImport = createRefToImportReducer(model.namespace);
reduceRefToImport(model.imports, toBeImported);
});
return models;
};
}
function sortInterfaces(interfaces: Interface[]) {
interfaces.sort((a, b) => (a.identifier > b.identifier ? 1 : -1));
}
export function createImportRefToInterfaceReducerCreator(params: ModelGeneratorParams) {
const { solution, types } = params;
const parseType = createTypeParser(removeTypeModifiers);
const simplifyType = createTypeSimplifier();
const getIdentifier = (type: string) => removeTypeModifiers(simplifyType(type));
const genericsCollector = new GenericsCollector(getIdentifier);
return reduceRefsToInterfaces;
function reduceRefsToInterfaces(interfaces: Interface[], ref: string): Interface[] {
const typeDef = types[ref];
if (!typeDef) return interfaces;
const namespace = parseNamespace(solution, ref);
let { baseType: base, genericArguments } = typeDef;
genericArguments = genericArguments || [];
let identifier = getIdentifier(ref);
identifier = replacePlaceholdersWithGenerics(identifier, genericArguments, genericsCollector);
if (base) {
if (extendsSelf(ref, base)) {
genericsCollector.collect(extractGenerics(base).generics, genericArguments);
return reduceRefsToInterfaces(interfaces, generateRefWithPlaceholders(base));
} else {
base = getIdentifier(base);
}
}
const { generics } = genericsCollector;
const _interface = new Interface({ identifier, base, namespace, ref, generics });
genericsCollector.reset();
typeDef.properties?.forEach(prop => {
let name = prop.jsonName || camel(prop.name);
name = shouldQuote(name) ? `'${name}'` : name;
const type = simplifyType(prop.typeSimple);
const refs = parseType(prop.type).reduce(
(acc: string[], r) => acc.concat(parseGenerics(r).toGenerics()),
[],
);
const property = new Property({ name, type, refs });
property.setOptional(isOptionalProperty(prop));
_interface.properties.push(property);
});
interfaces.push(_interface);
return [..._interface.properties, ..._interface.generics]
.reduce<string[]>((refs, prop) => {
prop.refs.forEach(type => {
if (types[type]?.isEnum) return;
if (interfaces.some(i => i.ref === type)) return;
refs.push(type);
});
return refs;
}, [])
.concat(base ? parseGenerics(typeDef.baseType!).toGenerics() : [])
.reduce(reduceRefsToInterfaces, interfaces);
}
}
export function createRefToImportReducerCreator(params: ModelGeneratorParams) {
const { solution } = params;
return (namespace: string) => createTypesToImportsReducer(solution, namespace);
}
function isOptionalProperty(prop: PropertyDef) {
return (
prop.typeSimple.endsWith('?') || (prop.typeSimple === 'string' && prop.isRequired === false)
);
}

20
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/namespace.ts

@ -0,0 +1,20 @@
import { createTypeParser, removeGenerics } from './type';
export function parseNamespace(solution: string, type: string) {
const parseType = createTypeParser(removeGenerics);
let namespace = parseType(type)[0]
.split('.')
.slice(0, -1)
.join('.');
if (solution === namespace) return '';
solution.split('.').reduceRight((acc, part) => {
acc = `${part}\\.${acc}`;
const regex = new RegExp(`^${acc}(Controllers\\.)?`);
namespace = namespace.replace(regex, '');
return acc;
}, '');
return namespace;
}

39
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/path.ts

@ -0,0 +1,39 @@
import { strings } from '@angular-devkit/core';
import { kebab } from './text';
export function relativePathToEnum(namespace: string, enumNamespace: string, enumName: string) {
const path = calculateRelativePath(namespace, enumNamespace);
return path + `/${kebab(enumName)}.enum`;
}
export function relativePathToModel(namespace: string, modelNamespace: string) {
const path = calculateRelativePath(namespace, modelNamespace);
return path + '/models';
}
function calculateRelativePath(ns1: string, ns2: string) {
if (ns1 === ns2) return '.';
const parts1 = ns1 ? ns1.split('.') : [];
const parts2 = ns2 ? ns2.split('.') : [];
while (parts1.length && parts2.length) {
if (parts1[0] !== parts2[0]) break;
parts1.shift();
parts2.shift();
}
const up = '../'.repeat(parts1.length) || '.';
const down = parts2.reduce((acc, p) => acc + '/' + strings.dasherize(p), '');
return removeTrailingSlash(removeDoubleSlash(up + down));
}
function removeDoubleSlash(path: string) {
return path.replace(/\/{2,}/g, '/');
}
function removeTrailingSlash(path: string) {
return path.replace(/\/+$/, '');
}

35
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/rule.ts

@ -0,0 +1,35 @@
import {
apply,
callRule,
forEach,
MergeStrategy,
mergeWith,
Rule,
SchematicContext,
Source,
Tree,
} from '@angular-devkit/schematics';
export function applyWithOverwrite(source: Source, rules: Rule[]): Rule {
return (tree: Tree, _context: SchematicContext) => {
const rule = mergeWith(apply(source, [...rules, overwriteFileIfExists(tree)]));
return rule(tree, _context);
};
}
export function mergeAndAllowDelete(host: Tree, rule: Rule) {
return async (tree: Tree, context: SchematicContext) => {
const nextTree = await callRule(rule, tree, context).toPromise();
host.merge(nextTree, MergeStrategy.AllowDeleteConflict);
};
}
export function overwriteFileIfExists(tree: Tree): Rule {
return forEach(fileEntry => {
if (!tree.exists(fileEntry.path)) return fileEntry;
tree.overwrite(fileEntry.path, fileEntry.content);
return null;
});
}

122
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/service.ts

@ -0,0 +1,122 @@
import {
Action,
Body,
Controller,
Import,
Method,
Property,
Service,
ServiceGeneratorParams,
Signature,
Type,
TypeWithEnum,
} from '../models';
import { sortImports } from './import';
import { parseNamespace } from './namespace';
import { parseGenerics } from './tree';
import {
createTypeAdapter,
createTypeParser,
createTypesToImportsReducer,
removeTypeModifiers,
} from './type';
export function serializeParameters(parameters: Property[]) {
return parameters.map(p => p.name + p.optional + ': ' + p.type + p.default, '').join(', ');
}
export function createControllerToServiceMapper({
solution,
types,
apiName,
}: ServiceGeneratorParams) {
const mapActionToMethod = createActionToMethodMapper();
return (controller: Controller) => {
const name = controller.controllerName;
const namespace = parseNamespace(solution, controller.type);
const actions = Object.values(controller.actions);
const imports = actions.reduce(createActionToImportsReducer(solution, types, namespace), []);
imports.push(new Import({ path: '@abp/ng.core', specifiers: ['RestService'] }));
imports.push(new Import({ path: '@angular/core', specifiers: ['Injectable'] }));
sortImports(imports);
const methods = actions.map(mapActionToMethod);
sortMethods(methods);
return new Service({ apiName, imports, methods, name, namespace });
};
}
function sortMethods(methods: Method[]) {
methods.sort((a, b) => (a.signature.name > b.signature.name ? 1 : -1));
}
export function createActionToMethodMapper() {
const mapActionToBody = createActionToBodyMapper();
const mapActionToSignature = createActionToSignatureMapper();
return (action: Action) => {
const body = mapActionToBody(action);
const signature = mapActionToSignature(action);
return new Method({ body, signature });
};
}
export function createActionToBodyMapper() {
const adaptType = createTypeAdapter();
return ({ httpMethod, parameters, returnValue, url }: Action) => {
const responseType = adaptType(returnValue.typeSimple);
const body = new Body({ method: httpMethod, responseType, url });
parameters.forEach(body.registerActionParameter);
return body;
};
}
export function createActionToSignatureMapper() {
const adaptType = createTypeAdapter();
return (action: Action) => {
const signature = new Signature({ name: getMethodNameFromAction(action) });
signature.parameters = action.parametersOnMethod.map(p => {
const type = adaptType(p.typeSimple);
const parameter = new Property({ name: p.name, type });
parameter.setDefault(p.defaultValue);
parameter.setOptional(p.isOptional);
return parameter;
});
return signature;
};
}
function getMethodNameFromAction(action: Action): string {
return action.uniqueName.split('Async')[0];
}
function createActionToImportsReducer(
solution: string,
types: Record<string, Type>,
namespace: string,
) {
const mapTypesToImports = createTypesToImportsReducer(solution, namespace);
const parseType = createTypeParser(removeTypeModifiers);
return (imports: Import[], { parametersOnMethod, returnValue }: Action) =>
mapTypesToImports(
imports,
[returnValue, ...parametersOnMethod].reduce((acc: TypeWithEnum[], param) => {
parseType(param.type).forEach(paramType =>
parseGenerics(paramType)
.toGenerics()
.forEach(type => {
if (types[type]) acc.push({ type, isEnum: types[type].isEnum });
}),
);
return acc;
}, []),
);
}

190
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/source.ts

@ -0,0 +1,190 @@
import { SchematicsException, Tree } from '@angular-devkit/schematics';
import got from 'got';
import {
API_DEFINITION_ENDPOINT,
PROXY_CONFIG_PATH,
PROXY_PATH,
PROXY_WARNING,
PROXY_WARNING_PATH,
} from '../constants';
import { Exception } from '../enums';
import { ApiDefinition, GenerateProxySchema, Project, ProxyConfig, WriteOp } from '../models';
import { getAssignedPropertyFromObjectliteral } from './ast';
import { interpolate } from './common';
import { readEnvironment, resolveProject } from './workspace';
export function createApiDefinitionGetter(params: GenerateProxySchema) {
const apiName = params['api-name'] || 'default';
return async (host: Tree) => {
const source = await resolveProject(host, params.source!);
const sourceUrl = getSourceUrl(host, source, apiName);
return await getApiDefinition(sourceUrl);
};
}
async function getApiDefinition(sourceUrl: string) {
const url = sourceUrl + API_DEFINITION_ENDPOINT;
let body: ApiDefinition;
try {
({ body } = await got(url, {
responseType: 'json',
searchParams: { includeTypes: true },
https: { rejectUnauthorized: false },
}));
} catch ({ response }) {
// handle redirects
if (!response?.body || response.statusCode >= 400)
throw new SchematicsException(interpolate(Exception.NoApi, url));
body = response.body;
}
return body;
}
export function createRootNamespaceGetter(params: GenerateProxySchema) {
const apiName = params['api-name'] || 'default';
return async (tree: Tree) => {
const project = await resolveProject(tree, params.source!);
const environmentExpr = readEnvironment(tree, project.definition);
if (!environmentExpr)
throw new SchematicsException(interpolate(Exception.NoEnvironment, project.name));
let assignment = getAssignedPropertyFromObjectliteral(environmentExpr, [
'apis',
apiName,
'rootNamespace',
]);
if (!assignment)
assignment = getAssignedPropertyFromObjectliteral(environmentExpr, [
'apis',
'default',
'rootNamespace',
]);
if (!assignment)
throw new SchematicsException(interpolate(Exception.NoRootNamespace, project.name, apiName));
return assignment.replace(/[`'"]/g, '');
};
}
export function getSourceUrl(tree: Tree, project: Project, apiName: string) {
const environmentExpr = readEnvironment(tree, project.definition);
if (!environmentExpr)
throw new SchematicsException(interpolate(Exception.NoEnvironment, project.name));
let assignment = getAssignedPropertyFromObjectliteral(environmentExpr, ['apis', apiName, 'url']);
if (!assignment)
assignment = getAssignedPropertyFromObjectliteral(environmentExpr, ['apis', 'default', 'url']);
if (!assignment)
throw new SchematicsException(interpolate(Exception.NoApiUrl, project.name, apiName));
return assignment.replace(/[`'"]/g, '');
}
export function createProxyConfigReader(targetPath: string) {
targetPath += PROXY_CONFIG_PATH;
return (tree: Tree) => {
try {
const buffer = tree.read(targetPath);
return JSON.parse(buffer!.toString()) as ProxyConfig;
} catch (_) {}
throw new SchematicsException(interpolate(Exception.NoProxyConfig, targetPath));
};
}
export function createProxyClearer(targetPath: string) {
targetPath += PROXY_PATH;
const proxyIndexPath = `${targetPath}/index.ts`;
return (tree: Tree) => {
try {
tree.getDir(targetPath).subdirs.forEach(dirName => {
const dirPath = `${targetPath}/${dirName}`;
tree.getDir(dirPath).visit(filePath => tree.delete(filePath));
tree.delete(dirPath);
});
if (tree.exists(proxyIndexPath)) tree.delete(proxyIndexPath);
return tree;
} catch (_) {
throw new SchematicsException(interpolate(Exception.DirRemoveFailed, targetPath));
}
};
}
export function createProxyWarningSaver(targetPath: string) {
targetPath += PROXY_WARNING_PATH;
const createFileWriter = createFileWriterCreator(targetPath);
return (tree: Tree) => {
const op = tree.exists(targetPath) ? 'overwrite' : 'create';
const writeWarningMD = createFileWriter(op, PROXY_WARNING);
writeWarningMD(tree);
return tree;
};
}
export function createProxyConfigSaver(apiDefinition: ApiDefinition, targetPath: string) {
const createProxyConfigJson = createProxyConfigJsonCreator(apiDefinition);
const readPreviousConfig = createProxyConfigReader(targetPath);
const createProxyConfigWriter = createProxyConfigWriterCreator(targetPath);
targetPath += PROXY_CONFIG_PATH;
return (tree: Tree) => {
const generated: string[] = [];
let op: WriteOp = 'create';
if (tree.exists(targetPath)) {
op = 'overwrite';
try {
readPreviousConfig(tree).generated.forEach(m => generated.push(m));
} catch (_) {}
}
const json = createProxyConfigJson(generated);
const writeProxyConfig = createProxyConfigWriter(op, json);
writeProxyConfig(tree);
return tree;
};
}
export function createProxyConfigWriterCreator(targetPath: string) {
targetPath += PROXY_CONFIG_PATH;
return createFileWriterCreator(targetPath);
}
export function createFileWriterCreator(targetPath: string) {
return (op: WriteOp, data: string) => (tree: Tree) => {
try {
tree[op](targetPath, data);
return tree;
} catch (_) {}
throw new SchematicsException(interpolate(Exception.FileWriteFailed, targetPath));
};
}
export function createProxyConfigJsonCreator(apiDefinition: ApiDefinition) {
return (generated: string[]) => generateProxyConfigJson({ generated, ...apiDefinition });
}
export function generateProxyConfigJson(proxyConfig: ProxyConfig) {
return JSON.stringify(proxyConfig, null, 2);
}

56
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/text.ts

@ -0,0 +1,56 @@
import { strings } from '@angular-devkit/core';
export const lower = (text: string) => text.toLowerCase();
export const upper = (text: string) => text.toUpperCase();
export const camel = (text: string) => toCamelCase(_(text));
export const pascal = (text: string) => strings.classify(_(text));
export const kebab = (text: string) => strings.dasherize(_(text));
export const snake = (text: string) => strings.underscore(_(text));
export const macro = (text: string) => upper(snake(text));
export const dir = (text: string) =>
strings.dasherize(text.replace(/\./g, '/').replace(/\/\//g, '/'));
export const quote = (value: number | string) =>
typeof value === 'string' ? `'${value.replace(/'/g, '\\\'')}'` : value;
function _(text: string): string {
return text.replace(/\./g, '_');
}
// https://github.com/JamesNK/Newtonsoft.Json/blob/master/Src/Newtonsoft.Json/Utilities/StringUtils.cs#L155
function toCamelCase(str: string) {
if (!str || !isUpperCase(str[0])) return str;
const chars = str.split('');
const { length } = chars;
for (let i = 0; i < length; i++) {
if (i === 1 && !isUpperCase(chars[i])) break;
const hasNext = i + 1 < length;
if (i > 0 && hasNext && !isUpperCase(chars[i + 1])) {
if (isSeparator(chars[i + 1])) {
chars[i] = toLowerCase(chars[i]);
}
break;
}
chars[i] = toLowerCase(chars[i]);
}
return chars.join('');
}
function isSeparator(str = '') {
return /[\s\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,\-.\/:;<=>?@\[\]^_`{|}~]+/.test(str);
}
function isUpperCase(str = '') {
return /[A-Z]+/.test(str);
}
function toLowerCase(str = '') {
return str.toLowerCase();
}

71
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/tree.ts

@ -0,0 +1,71 @@
export class TypeNode {
children: TypeNode[] = [];
index = 0;
constructor(
public data: string,
public parent: TypeNode | null,
public mapperFn = (node: TypeNode) => node.data,
) {}
toGenerics(): string[] {
const generics = this.children.length ? `<${this.children.map(n => `T${n.index}`)}>` : '';
return [this.data + generics].concat(
this.children.reduce((acc: string[], node) => acc.concat(node.toGenerics()), []),
);
}
toString() {
const self = this.mapperFn(this);
if (!self) return '';
const representation = self + this.children.filter(String).join(', ');
if (!this.parent) return representation;
const siblings = this.parent.children;
return (
(siblings[0] === this ? '<' : '') +
representation +
(siblings[siblings.length - 1] === this ? '>' : '')
);
}
valueOf() {
return this.toString();
}
}
export function parseGenerics(type: string, mapperFn?: TypeNodeMapperFn) {
const [rootType, ...types] = type.split('<');
const root = new TypeNode(rootType, null, mapperFn);
types.reduce((parent, t) => {
const [left, right] = t.split(/>+,?\s*/);
const leftNode = new TypeNode(left, parent, mapperFn);
leftNode.index = parent.children.length;
parent.children.push(leftNode);
parent = leftNode;
let { length } = t.match(/>/g) || [];
while (length--) parent = parent.parent!;
if (right) {
parent = parent.parent!;
const rightNode = new TypeNode(right, parent, mapperFn);
rightNode.index = parent.children.length;
parent.children.push(rightNode);
parent = rightNode;
}
return parent;
}, root);
return root;
}
export type TypeNodeMapperFn = (node: TypeNode) => string;

114
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/type.ts

@ -0,0 +1,114 @@
import { SYSTEM_TYPES, VOLO_REGEX } from '../constants';
import { eImportKeyword } from '../enums';
import { Import, TypeWithEnum } from '../models';
import { extractSimpleGenerics } from './generics';
import { parseNamespace } from './namespace';
import { relativePathToEnum, relativePathToModel } from './path';
import { parseGenerics } from './tree';
export function createTypeSimplifier() {
const parseType = createTypeParser(t => {
let type = t.replace(
/(?<![^<, ])System\.([0-9A-Za-z.]+)/g,
(_, match) => SYSTEM_TYPES.get(match) ?? 'any',
);
type = /any</.test(type) ? 'any' : type;
const { identifier, generics } = extractSimpleGenerics(type);
return generics.length ? `${identifier}<${generics.join(', ')}>` : identifier;
});
return (type: string) => {
const parsed = parseType(type);
const last = parsed.pop()!;
return parsed.reduceRight((record, tKey) => `Record<${tKey}, ${record}>`, last);
};
}
export function createTypeParser(replacerFn = (t: string) => t) {
const normalizeType = createTypeNormalizer(replacerFn);
return (originalType: string) => flattenDictionaryTypes([], originalType).map(normalizeType);
}
export function createTypeNormalizer(replacerFn = (t: string) => t) {
return (type: string) => {
return replacerFn(normalizeTypeAnnotations(type));
};
}
export function flattenDictionaryTypes(types: string[], type: string) {
type
.replace(/[}{]/g, '')
.split(':')
.forEach(t => types.push(t));
return types;
}
export function normalizeTypeAnnotations(type: string) {
return type.replace(/\[(.+)+\]/g, '$1[]').replace(/\?/g, '');
}
export function removeGenerics(type: string) {
return type.replace(/<.+>/g, '');
}
export function removeTypeModifiers(type: string) {
return type.replace(/\[\]/g, '');
}
export function createTypesToImportsReducer(solution: string, namespace: string) {
const mapTypeToImport = createTypeToImportMapper(solution, namespace);
return (imports: Import[], types: TypeWithEnum[]) => {
types.forEach(({ type, isEnum }) => {
const newImport = mapTypeToImport(type, isEnum);
if (!newImport) return;
const existingImport = imports.find(
({ keyword, path }) => keyword === newImport.keyword && path === newImport.path,
);
if (!existingImport) return imports.push(newImport);
existingImport.refs = [...new Set([...existingImport.refs, ...newImport.refs])];
existingImport.specifiers = [
...new Set([...existingImport.specifiers, ...newImport.specifiers]),
].sort();
});
return imports;
};
}
export function createTypeToImportMapper(solution: string, namespace: string) {
const adaptType = createTypeAdapter();
const simplifyType = createTypeSimplifier();
return (type: string, isEnum: boolean) => {
if (!type || type.startsWith('System')) return;
const modelNamespace = parseNamespace(solution, type);
const refs = [removeTypeModifiers(type)];
const specifiers = [adaptType(simplifyType(refs[0]).split('<')[0])];
const path = VOLO_REGEX.test(type)
? '@abp/ng.core'
: isEnum
? relativePathToEnum(namespace, modelNamespace, specifiers[0])
: relativePathToModel(namespace, modelNamespace);
return new Import({ keyword: eImportKeyword.Type, path, refs, specifiers });
};
}
export function createTypeAdapter() {
const simplifyType = createTypeSimplifier();
return (type: string) => parseGenerics(type, node => simplifyType(node.data)).toString();
}
// naming here is depictive only
export function extendsSelf(type: string, base: string) {
return removeGenerics(base) === removeGenerics(type);
}

66
npm/ng-packs/nx/ng-packs/packages/schematics/src/utils/workspace.ts

@ -0,0 +1,66 @@
import { strings, workspaces } from '@angular-devkit/core';
import { SchematicsException, Tree } from '@angular-devkit/schematics';
import { Exception } from '../enums';
import { Project } from '../models';
import { getWorkspace, getWorkspaceSchema, ProjectType, WorkspaceSchema } from './angular';
import { findEnvironmentExpression } from './ast';
import { readFileInTree } from './common';
export function isLibrary(project: workspaces.ProjectDefinition): boolean {
return project.extensions['projectType'] === ProjectType.Library;
}
export function readEnvironment(tree: Tree, project: workspaces.ProjectDefinition) {
if (isLibrary(project)) return undefined;
const srcPath = project.sourceRoot || `${project.root}/src`;
const envPath = srcPath + '/environments/environment.ts';
const source = readFileInTree(tree, envPath);
return findEnvironmentExpression(source);
}
export function readWorkspaceSchema(tree: Tree) {
if (!tree.exists('/angular.json')) throw new SchematicsException(Exception.NoWorkspace);
let workspaceSchema: WorkspaceSchema;
try {
workspaceSchema = getWorkspaceSchema(tree);
} catch (_) {
throw new SchematicsException(Exception.InvalidWorkspace);
}
return workspaceSchema;
}
export async function resolveProject(tree: Tree, name: string): Promise<Project> {
name = name || readWorkspaceSchema(tree).defaultProject!;
const workspace = await getWorkspace(tree);
let definition: Project['definition'] | undefined;
try {
definition = workspace.projects.get(name);
} catch (_) {}
if (!definition)
try {
name = strings.dasherize(name);
definition = workspace.projects.get(name);
} catch (_) {}
if (!definition)
try {
name = strings.camelize(name);
definition = workspace.projects.get(name);
} catch (_) {}
if (!definition)
try {
name = strings.classify(name);
definition = workspace.projects.get(name);
} catch (_) {}
if (!definition) throw new SchematicsException(Exception.NoProject);
return { name, definition };
}

24
npm/ng-packs/nx/ng-packs/packages/schematics/tsconfig.json

@ -0,0 +1,24 @@
{
"compilerOptions": {
"baseUrl": "tsconfig",
"lib": ["es2018", "dom"],
"declaration": true,
"module": "commonjs",
"moduleResolution": "node",
"noEmitOnError": true,
"noFallthroughCasesInSwitch": true,
"noImplicitAny": true,
"noImplicitThis": true,
"noUnusedParameters": true,
"noUnusedLocals": true,
"rootDir": "src/",
"skipDefaultLibCheck": true,
"skipLibCheck": true,
"sourceMap": true,
"strictNullChecks": true,
"target": "es2017",
"types": ["jest", "node"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "src/*/files/**/*", "**/*.spec.ts"]
}

10
npm/ng-packs/nx/ng-packs/packages/schematics/tsconfig.spec.json

@ -0,0 +1,10 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["jest", "node"]
},
"files": ["src/test-setup.ts"],
"include": ["**/*.spec.ts", "**/*.d.ts"]
}

8
npm/ng-packs/nx/ng-packs/tsconfig.base.json

@ -32,15 +32,17 @@
"@abp/ng.setting-management/config": [
"dist/packages/setting-management/config"
],
"@abp/ng.tenant-management": ["dist/packages/tenant-management"],
"@abp/ng.tenant-management/config": [
"dist/packages/tenant-management/config"
],
"@abp/ng.theme.basic": ["dist/packages/theme-basic"],
"@abp/ng.theme.basic/testing": ["dist/packages/theme-basic/testing"],
"@abp/ng.theme.shared": ["dist/packages/theme-shared"],
"@abp/ng.theme.shared/extensions": [
"dist/packages/theme-shared/extensions"
],
"@abp/ng.theme.shared/testing": ["dist/packages/theme-shared/testing"],
"@abp/ng.tenant-management": ["dist/packages/tenant-management"],
"@abp/ng.tenant-management/config": ["dist/packages/tenant-management/config"]
"@abp/ng.theme.shared/testing": ["dist/packages/theme-shared/testing"]
}
},
"exclude": ["node_modules", "tmp"]

Loading…
Cancel
Save