Browse Source

Merge pull request #5137 from abpframework/feat/4881

Replaced Angular Client Proxy Generation code with Angular Schematics
pull/5141/head
Mehmet Erim 6 years ago
committed by GitHub
parent
commit
97a6cacb78
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      npm/ng-packs/.gitignore
  2. 18
      npm/ng-packs/.vscode/settings.json
  3. 29
      npm/ng-packs/angular.json
  4. 5
      npm/ng-packs/package.json
  5. 18
      npm/ng-packs/packages/schematics/.gitignore
  6. 3
      npm/ng-packs/packages/schematics/.npmignore
  7. 3
      npm/ng-packs/packages/schematics/README.md
  8. 7
      npm/ng-packs/packages/schematics/jest.config.js
  9. 7
      npm/ng-packs/packages/schematics/ng-package.json
  10. 24
      npm/ng-packs/packages/schematics/package.json
  11. 14
      npm/ng-packs/packages/schematics/src/collection.json
  12. 8
      npm/ng-packs/packages/schematics/src/commands/api/files-enum/shared/enums/__namespace@dir__/__name@kebab__.ts.template
  13. 10
      npm/ng-packs/packages/schematics/src/commands/api/files-model/shared/models/__namespace@dir__/index.ts.template
  14. 23
      npm/ng-packs/packages/schematics/src/commands/api/files-service/shared/services/__namespace@dir__/__name@kebab__.service.ts.template
  15. 157
      npm/ng-packs/packages/schematics/src/commands/api/index.ts
  16. 39
      npm/ng-packs/packages/schematics/src/commands/api/schema.json
  17. 16
      npm/ng-packs/packages/schematics/src/commands/api/schema.ts
  18. 40
      npm/ng-packs/packages/schematics/src/commands/proxy/index.ts
  19. 49
      npm/ng-packs/packages/schematics/src/commands/proxy/schema.json
  20. 21
      npm/ng-packs/packages/schematics/src/commands/proxy/schema.ts
  21. 1
      npm/ng-packs/packages/schematics/src/constants/api.ts
  22. 3
      npm/ng-packs/packages/schematics/src/constants/index.ts
  23. 23
      npm/ng-packs/packages/schematics/src/constants/system-types.ts
  24. 1
      npm/ng-packs/packages/schematics/src/constants/volo.ts
  25. 6
      npm/ng-packs/packages/schematics/src/enums/binding-source-id.ts
  26. 12
      npm/ng-packs/packages/schematics/src/enums/exception.ts
  27. 4
      npm/ng-packs/packages/schematics/src/enums/import-keyword.ts
  28. 4
      npm/ng-packs/packages/schematics/src/enums/index.ts
  29. 6
      npm/ng-packs/packages/schematics/src/enums/method-modifier.ts
  30. 11519
      npm/ng-packs/packages/schematics/src/mocks/api-definition.json
  31. 80
      npm/ng-packs/packages/schematics/src/models/api-definition.ts
  32. 16
      npm/ng-packs/packages/schematics/src/models/import.ts
  33. 7
      npm/ng-packs/packages/schematics/src/models/index.ts
  34. 73
      npm/ng-packs/packages/schematics/src/models/method.ts
  35. 43
      npm/ng-packs/packages/schematics/src/models/model.ts
  36. 6
      npm/ng-packs/packages/schematics/src/models/project.ts
  37. 27
      npm/ng-packs/packages/schematics/src/models/service.ts
  38. 2
      npm/ng-packs/packages/schematics/src/models/util.ts
  39. 5
      npm/ng-packs/packages/schematics/src/utils/angular/README.md
  40. 753
      npm/ng-packs/packages/schematics/src/utils/angular/ast-utils.ts
  41. 127
      npm/ng-packs/packages/schematics/src/utils/angular/change.ts
  42. 532
      npm/ng-packs/packages/schematics/src/utils/angular/config.ts
  43. 76
      npm/ng-packs/packages/schematics/src/utils/angular/dependencies.ts
  44. 151
      npm/ng-packs/packages/schematics/src/utils/angular/find-module.ts
  45. 17
      npm/ng-packs/packages/schematics/src/utils/angular/index.ts
  46. 82
      npm/ng-packs/packages/schematics/src/utils/angular/json-file.ts
  47. 231
      npm/ng-packs/packages/schematics/src/utils/angular/json-utils.ts
  48. 26
      npm/ng-packs/packages/schematics/src/utils/angular/latest-versions.ts
  49. 51
      npm/ng-packs/packages/schematics/src/utils/angular/lint-fix.ts
  50. 87
      npm/ng-packs/packages/schematics/src/utils/angular/ng-ast-utils.ts
  51. 25
      npm/ng-packs/packages/schematics/src/utils/angular/parse-name.ts
  52. 19
      npm/ng-packs/packages/schematics/src/utils/angular/paths.ts
  53. 13
      npm/ng-packs/packages/schematics/src/utils/angular/project-targets.ts
  54. 70
      npm/ng-packs/packages/schematics/src/utils/angular/tsconfig.ts
  55. 77
      npm/ng-packs/packages/schematics/src/utils/angular/validation.ts
  56. 171
      npm/ng-packs/packages/schematics/src/utils/angular/workspace-models.ts
  57. 91
      npm/ng-packs/packages/schematics/src/utils/angular/workspace.ts
  58. 37
      npm/ng-packs/packages/schematics/src/utils/ast.ts
  59. 25
      npm/ng-packs/packages/schematics/src/utils/common.ts
  60. 43
      npm/ng-packs/packages/schematics/src/utils/enum.ts
  61. 11
      npm/ng-packs/packages/schematics/src/utils/import.ts
  62. 15
      npm/ng-packs/packages/schematics/src/utils/index.ts
  63. 148
      npm/ng-packs/packages/schematics/src/utils/model.ts
  64. 18
      npm/ng-packs/packages/schematics/src/utils/namespace.ts
  65. 21
      npm/ng-packs/packages/schematics/src/utils/path.ts
  66. 26
      npm/ng-packs/packages/schematics/src/utils/rule.ts
  67. 122
      npm/ng-packs/packages/schematics/src/utils/service.ts
  68. 68
      npm/ng-packs/packages/schematics/src/utils/source.ts
  69. 15
      npm/ng-packs/packages/schematics/src/utils/text.ts
  70. 71
      npm/ng-packs/packages/schematics/src/utils/tree.ts
  71. 103
      npm/ng-packs/packages/schematics/src/utils/type.ts
  72. 67
      npm/ng-packs/packages/schematics/src/utils/workspace.ts
  73. 24
      npm/ng-packs/packages/schematics/tsconfig.json
  74. 13
      npm/ng-packs/packages/schematics/tslint.json
  75. 63
      npm/ng-packs/scripts/build-schematics.ts
  76. 2
      npm/ng-packs/scripts/build.ts
  77. 1
      npm/ng-packs/scripts/package.json
  78. 142
      npm/ng-packs/yarn.lock

5
npm/ng-packs/.gitignore

@ -1,5 +1,8 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# generic ignore files
*.gitignore.*
# compiled output
/tmp
/out-tsc
@ -46,4 +49,4 @@ testem.log
Thumbs.db
!**/[Pp]ackages/*
*.internal.*
*.internal.*

18
npm/ng-packs/.vscode/settings.json

@ -1,4 +1,22 @@
{
"[typescriptreact]": {
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll": false,
"source.organizeImports": false
}
},
"[xml]": {
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
"source.fixAll": false,
"source.organizeImports": false
}
},
"files.associations": {
"*.ts.template": "typescriptreact",
"*.html.template": "xml"
},
"search.exclude": {
"**/dist": true
},

29
npm/ng-packs/angular.json

@ -366,6 +366,35 @@
}
}
},
"schematics": {
"projectType": "library",
"root": "packages/schematics",
"sourceRoot": "packages/schematics/src",
"prefix": "abp",
"architect": {
"build": {
"options": {
"tsConfig": "packages/schematics/tsconfig.lib.json",
"project": "packages/schematics/ng-package.json"
}
},
"test": {
"builder": "@angular-builders/jest:run",
"options": {
"tsConfig": "tsconfig.json",
"coverage": true,
"passWithNoTests": true
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": ["packages/schematics/tsconfig.json"],
"exclude": ["**/node_modules/**"]
}
}
}
},
"dev-app": {
"projectType": "application",
"schematics": {

5
npm/ng-packs/package.json

@ -13,6 +13,8 @@
"test": "ng test --watchAll --runInBand",
"commit": "git-cz",
"lint": "ng lint",
"build:schematics": "cd scripts && yarn && yarn build:schematics && cd ..",
"dev:schematics": "tsc -p packages/schematics/tsconfig.json -w",
"scripts:build": "cd scripts && yarn && yarn build",
"prepare:workspace": "yarn scripts:build --noInstall",
"ci": "yarn ng lint && yarn prepare:workspace && yarn ci:test && yarn ci:build",
@ -57,6 +59,7 @@
"@ngxs/router-plugin": "^3.6.2",
"@ngxs/storage-plugin": "^3.6.2",
"@ngxs/store": "^3.6.2",
"@schematics/angular": "~10.0.5",
"@swimlane/ngx-datatable": "^17.1.0",
"@types/jest": "^25.2.3",
"@types/node": "^12.11.1",
@ -67,9 +70,11 @@
"conventional-changelog-cli": "^2.0.31",
"cz-conventional-changelog": "3.0.2",
"font-awesome": "^4.7.0",
"got": "^11.5.2",
"jest": "^25.0.0",
"jest-canvas-mock": "^2.2.0",
"jest-preset-angular": "^8.2.0",
"jsonc-parser": "^2.3.0",
"just-clone": "^3.1.0",
"just-compare": "^1.3.0",
"lerna": "^3.19.0",

18
npm/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/packages/schematics/.npmignore

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

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

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

7
npm/ng-packs/packages/schematics/jest.config.js

@ -0,0 +1,7 @@
const jestConfig = require('../../jest.config');
module.exports = {
...jestConfig,
name: 'schematics',
testMatch: ['<rootDir>/packages/schematics/**/+(*.)+(spec).+(ts)'],
};

7
npm/ng-packs/packages/schematics/ng-package.json

@ -0,0 +1,7 @@
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/schematics",
"lib": {
"entryFile": "src/public-api.ts"
}
}

24
npm/ng-packs/packages/schematics/package.json

@ -0,0 +1,24 @@
{
"name": "@abp/ng.schematics",
"version": "3.0.5",
"description": "Schematics that works with ABP Suite",
"keywords": [
"schematics"
],
"author": "",
"license": "MIT",
"schematics": "./src/collection.json",
"dependencies": {
"@angular-devkit/core": "~10.0.3",
"@angular-devkit/schematics": "~10.0.3",
"got": "^11.5.2",
"typescript": "~3.9.2"
},
"devDependencies": {
"@types/node": "^12.11.1",
"@types/jest": "^26.0.0",
"jest": "^26.0.0",
"jest-preset-angular": "^8.2.0",
"@schematics/angular": "~10.0.3"
}
}

14
npm/ng-packs/packages/schematics/src/collection.json

@ -0,0 +1,14 @@
{
"schematics": {
"proxy": {
"description": "ABP Proxy Generator Schematics",
"factory": "./commands/proxy",
"schema": "./commands/proxy/schema.json"
},
"api": {
"description": "ABP API Generator Schematics",
"factory": "./commands/api",
"schema": "./commands/api/schema.json"
}
}
}

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

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

10
npm/ng-packs/packages/schematics/src/commands/api/files-model/shared/models/__namespace@dir__/index.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 %>;<% } %>
}
<% } %>

23
npm/ng-packs/packages/schematics/src/commands/api/files-service/shared/services/__namespace@dir__/__name@kebab__.service.ts.template

@ -0,0 +1,23 @@
<% 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 %>',
url: `/<%= body.url %>`,<%
if (body.params.length) { %>
params: { <%= body.params.join(', ') %> },<% }
if (body.body) { %>
body: <%= body.body %>,<% } %>
},
{ apiName: this.apiName });<% } %>
constructor(private restService: RestService) {}
}

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

@ -0,0 +1,157 @@
import { normalize, strings } from '@angular-devkit/core';
import {
applyTemplates,
branchAndMerge,
chain,
move,
SchematicContext,
SchematicsException,
Tree,
url,
} from '@angular-devkit/schematics';
import { Exception } from '../../enums';
import { ServiceGeneratorParams } from '../../models';
import {
applyWithOverwrite,
buildDefaultPath,
createApiDefinitionReader,
createControllerToServiceMapper,
createImportRefsToModelReducer,
createImportRefToEnumMapper,
EnumGeneratorParams,
getEnumNamesFromImports,
interpolate,
ModelGeneratorParams,
resolveProject,
serializeParameters,
} from '../../utils';
import * as cases from '../../utils/text';
import { Schema as GenerateProxySchema } from './schema';
export default function(params: GenerateProxySchema) {
const solution = params.solution;
const moduleName = strings.camelize(params.module || 'app');
return chain([
async (tree: Tree, _context: SchematicContext) => {
const target = await resolveProject(tree, params.target!);
const targetPath = buildDefaultPath(target.definition);
const readApiDefinition = createApiDefinitionReader(
`${targetPath}/shared/api-definition.json`,
);
const data = readApiDefinition(tree);
const types = data.types;
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,
});
return branchAndMerge(chain([generateServices, generateModels, generateEnums]));
},
]);
}
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)),
]);
}),
);
}

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

@ -0,0 +1,39 @@
{
"$schema": "http://json-schema.org/schema",
"id": "SchematicsAbpGenerateAPI",
"title": "ABP Generate API Schema",
"type": "object",
"properties": {
"solution": {
"alias": "x",
"description": "Solution name",
"type": "string",
"$default": {
"$source": "argv",
"index": 0
},
"x-prompt": "Please enter the solution name. (case-sensitive, eg. Acme.BookStore)"
},
"target": {
"alias": "t",
"description": "Angular project to generate code in",
"type": "string",
"$default": {
"$source": "argv",
"index": 1
},
"x-prompt": "Plese enter Angular project name to place generated code in. (default: workspace \"defaultProject\")"
},
"module": {
"alias": "m",
"description": "Backend module to generate code for",
"type": "string",
"$default": {
"$source": "argv",
"index": 2
},
"x-prompt": "Please enter name of the backend module you wish to generate proxies for. (default: \"app\")"
}
},
"required": ["solution"]
}

16
npm/ng-packs/packages/schematics/src/commands/api/schema.ts

@ -0,0 +1,16 @@
export interface Schema {
/**
* Solution name
*/
solution: string;
/**
* Angular project to generate code in
*/
target?: string;
/**
* Backend module to generate code for
*/
module?: string;
}

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

@ -0,0 +1,40 @@
import { strings } from '@angular-devkit/core';
import {
branchAndMerge,
chain,
schematic,
SchematicContext,
Tree,
} from '@angular-devkit/schematics';
import { API_DEFINITION_ENDPOINT } from '../../constants';
import { ApiDefinition } from '../../models';
import {
buildDefaultPath,
createApiDefinitionSaver,
getApiDefinition,
getSourceUrl,
resolveProject,
} from '../../utils';
import { Schema as GenerateProxySchema } from './schema';
export default function(params: GenerateProxySchema) {
const moduleName = strings.camelize(params.module || 'app');
return chain([
async (tree: Tree, _context: SchematicContext) => {
const source = await resolveProject(tree, params.source!);
const target = await resolveProject(tree, params.target!);
const sourceUrl = getSourceUrl(tree, source, moduleName);
const targetPath = buildDefaultPath(target.definition);
const data: ApiDefinition = await getApiDefinition(sourceUrl + API_DEFINITION_ENDPOINT);
const saveApiDefinition = createApiDefinitionSaver(
data,
`${targetPath}/shared/api-definition.json`,
);
const createApi = schematic('api', params);
return branchAndMerge(chain([saveApiDefinition, createApi]));
},
]);
}

49
npm/ng-packs/packages/schematics/src/commands/proxy/schema.json

@ -0,0 +1,49 @@
{
"$schema": "http://json-schema.org/schema",
"id": "SchematicsAbpGenerateProxy",
"title": "ABP Generate Proxy Schema",
"type": "object",
"properties": {
"solution": {
"alias": "x",
"description": "Solution name",
"type": "string",
"$default": {
"$source": "argv",
"index": 0
},
"x-prompt": "Please enter the solution name. (case-sensitive, eg. Acme.BookStore)"
},
"source": {
"alias": "s",
"description": "Angular project to resolve API definition URL from",
"type": "string",
"$default": {
"$source": "argv",
"index": 1
},
"x-prompt": "Plese enter Angular project name to resolve API definition URL from. (default: workspace \"defaultProject\")"
},
"target": {
"alias": "t",
"description": "Angular project to generate code in",
"type": "string",
"$default": {
"$source": "argv",
"index": 2
},
"x-prompt": "Plese enter Angular project name to place generated code in. (default: workspace \"defaultProject\")"
},
"module": {
"alias": "m",
"description": "Backend module to generate code for",
"type": "string",
"$default": {
"$source": "argv",
"index": 3
},
"x-prompt": "Please enter name of the backend module you wish to generate proxies for. (default: \"app\")"
}
},
"required": ["solution"]
}

21
npm/ng-packs/packages/schematics/src/commands/proxy/schema.ts

@ -0,0 +1,21 @@
export interface Schema {
/**
* Solution name
*/
solution: string;
/**
* Angular project to resolve API definition URL from
*/
source?: string;
/**
* Angular project to generate code in
*/
target?: string;
/**
* Backend module to generate code for
*/
module?: string;
}

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

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

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

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

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

@ -0,0 +1,23 @@
export const SYSTEM_TYPES = new Map([
['Bool', 'boolean'],
['Byte', 'number'],
['Char', 'string'],
['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/packages/schematics/src/constants/volo.ts

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

6
npm/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',
}

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

@ -0,0 +1,12 @@
export const enum Exception {
FileNotFound = '[File Not Found] There is no file at "{0}" path.',
InvalidModule = '[Invalid Module] Backend module "{0}" does not exist in API definition.',
InvalidWorkspace = '[Invalid Workspace] The angular.json should be a valid JSON file.',
NoApi = '[API Not Available] Please double-check the URL in the source project environment and make sure your application is up and running.',
NoApiDefinition = '[API Definition Not Found] There is no valid API definition file at "{0}".',
NoProject = '[Project Not Found] Either define a default project in your workspace or specify the project name in schematics options.',
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}" module from "{0}" project.',
}

4
npm/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/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/packages/schematics/src/enums/method-modifier.ts

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

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

File diff suppressed because it is too large

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

@ -0,0 +1,80 @@
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;
type: string;
typeSimple: string;
}
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;
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;
}

16
npm/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'>;

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

@ -0,0 +1,7 @@
export * from './api-definition';
export * from './import';
export * from './method';
export * from './model';
export * from './project';
export * from './service';
export * from './util';

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

@ -0,0 +1,73 @@
import { strings } from '@angular-devkit/core';
import { eBindingSourceId, eMethodModifier } from '../enums';
import { ParameterInBody } from './api-definition';
import { Property } from './model';
import { Omissible } from './util';
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, name, nameOnMethod } = param;
const camelName = strings.camelize(name);
const value = descriptorName ? `${descriptorName}.${camelName}` : nameOnMethod;
switch (bindingSourceId) {
case eBindingSourceId.Model:
case eBindingSourceId.Query:
this.params.push(`${camelName}: ${value}`);
break;
case eBindingSourceId.Body:
this.body = value;
break;
case eBindingSourceId.Path:
const regex = new RegExp('{' + camelName + '}', 'g');
this.url = this.url.replace(regex, '${' + value + '}');
break;
default:
break;
}
};
constructor(options: BodyOptions) {
Object.assign(this, options);
}
}
export type BodyOptions = Omissible<
Omit<Body, 'registerActionParameter'>,
'params' | 'requestType'
>;

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

@ -0,0 +1,43 @@
import { Import } from './import';
import { Omissible } from './util';
export class Model {
imports: Import[] = [];
interfaces: Interface[] = [];
namespace: string;
path: string;
constructor(options: ModelOptions) {
Object.assign(this, options);
}
}
export type ModelOptions = Omissible<Model, 'imports' | 'interfaces'>;
export class Interface {
base: string | null;
identifier: string;
namespace: string;
properties: Property[] = [];
ref: string;
constructor(options: InterfaceOptions) {
Object.assign(this, options);
}
}
export type InterfaceOptions = Omissible<Interface, 'properties'>;
export class Property {
name: string;
type: string;
default = '';
optional: '' | '?' = '';
refs: string[] = [];
constructor(options: PropertyOptions) {
Object.assign(this, options);
}
}
export type PropertyOptions = Omissible<Property, 'default' | 'optional' | 'refs'>;

6
npm/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;
}

27
npm/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'>;

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

@ -0,0 +1,2 @@
// Omissible (given keys will become optional)
export type Omissible<T, K extends keyof T> = Partial<Pick<T, K>> & Omit<T, K>;

5
npm/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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;
}

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

@ -0,0 +1,171 @@
/**
* @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 { experimental } from '@angular-devkit/core';
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',
NgPackagr = '@angular-devkit/build-ng-packagr:build',
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 extends experimental.workspace.WorkspaceSchema {
projects: {
[key: string]: WorkspaceProject<ProjectType.Application | ProjectType.Library>;
};
}
export interface WorkspaceProject<TProjectType extends ProjectType = ProjectType.Application>
extends experimental.workspace.WorkspaceProject {
/**
* Project type.
*/
projectType: ProjectType;
/**
* 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/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);
}

37
npm/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
);
}

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

@ -0,0 +1,25 @@
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);
}

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

@ -0,0 +1,43 @@
import { SchematicsException } from '@angular-devkit/schematics';
import { Exception } from '../enums';
import { Type } from '../models';
import { interpolate } from './common';
import { parseNamespace } from './namespace';
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.includes('/enums/');
}
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, value: enumValues[i] }));
return {
namespace,
name: ref.split('.').pop()!,
members,
};
};
}

11
npm/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, '');
}

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

@ -0,0 +1,15 @@
export * from './angular';
export * from './ast';
export * from './common';
export * from './enum';
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';

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

@ -0,0 +1,148 @@
import { strings } from '@angular-devkit/core';
import { VOLO_REGEX } from '../constants';
import { Interface, Model, Property, Type, TypeWithEnum } from '../models';
import { parseNamespace } from './namespace';
import { relativePathToModel } from './path';
import { parseGenerics } from './tree';
import {
createTypeParser,
createTypeSimplifier,
createTypesToImportsReducer,
removeTypeModifiers,
} from './type';
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.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));
return reduceRefsToInterfaces;
function reduceRefsToInterfaces(interfaces: Interface[], ref: string) {
const typeDef = types[ref];
if (!typeDef) return interfaces;
const namespace = parseNamespace(solution, ref);
const identifier = (typeDef.genericArguments ?? []).reduce(
(acc, t, i) => acc.replace(`T${i}`, t),
getIdentifier(ref),
);
const base = typeDef.baseType ? getIdentifier(typeDef.baseType) : null;
const _interface = new Interface({ identifier, base, namespace, ref });
typeDef.properties?.forEach(prop => {
const name = strings.camelize(prop.name);
const optional = prop.typeSimple.endsWith('?') ? '?' : '';
const type = simplifyType(prop.typeSimple);
const refs = parseType(prop.type).reduce(
(acc: string[], r) => acc.concat(parseGenerics(r).toGenerics()),
[],
);
_interface.properties.push(new Property({ name, optional, type, refs }));
});
interfaces.push(_interface);
return _interface.properties
.reduce<string[]>((refs, prop) => {
prop.refs.forEach(type => !types[type]?.isEnum && refs.push(type));
return refs;
}, [])
.concat(
base
? parseGenerics(typeDef.baseType!)
.toGenerics()
.join('')
: [],
)
.reduce<Interface[]>(reduceRefsToInterfaces, interfaces);
}
}
export function createRefToImportReducerCreator(params: ModelGeneratorParams) {
const { solution } = params;
return (namespace: string) => createTypesToImportsReducer(solution, namespace);
}

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

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

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

@ -0,0 +1,21 @@
import { dir, kebab } from './text';
export function relativePathToEnum(namespace: string, enumNamespace: string, enumName: string) {
const repeats = namespace ? namespace.split('.').length : 0;
const path = '..' + '/..'.repeat(repeats) + '/enums/' + dir(enumNamespace);
return removeDoubleSlash(path + '/' + kebab(enumName));
}
export function relativePathToModel(namespace: string, modelNamespace: string) {
const repeats = namespace ? namespace.split('.').length : 0;
const path = '..' + '/..'.repeat(repeats) + '/models/' + dir(modelNamespace);
return removeTrailingSlash(path);
}
function removeDoubleSlash(path: string) {
return path.replace(/\/{2,}/g, '/');
}
function removeTrailingSlash(path: string) {
return path.replace(/\/+$/, '');
}

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

@ -0,0 +1,26 @@
import {
apply,
forEach,
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 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/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 });
if (p.defaultValue) parameter.default = ` = ${p.defaultValue}`;
else if (p.isOptional) parameter.optional = '?';
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;
}, []),
);
}

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

@ -0,0 +1,68 @@
import { SchematicsException, Tree } from '@angular-devkit/schematics';
import got from 'got';
import { Exception } from '../enums';
import { ApiDefinition, Project } from '../models';
import { getAssignedPropertyFromObjectliteral } from './ast';
import { interpolate } from './common';
import { readEnvironment } from './workspace';
export async function getApiDefinition(url: string) {
let body: any;
try {
({ body } = await got(url, {
responseType: 'json',
searchParams: { includeTypes: true },
https: { rejectUnauthorized: false },
}));
} catch ({ response }) {
// handle redirects
if (response?.body && response.statusCode < 400) return response.body;
throw new SchematicsException(Exception.NoApi);
}
return body;
}
export function getSourceUrl(tree: Tree, project: Project, moduleName: string) {
const environmentExpr = readEnvironment(tree, project.definition);
if (!environmentExpr)
throw new SchematicsException(interpolate(Exception.NoEnvironment, project.name));
let assignment = getAssignedPropertyFromObjectliteral(environmentExpr, [
'apis',
moduleName,
'url',
]);
if (!assignment)
assignment = getAssignedPropertyFromObjectliteral(environmentExpr, ['apis', 'default', 'url']);
if (!assignment)
throw new SchematicsException(interpolate(Exception.NoApiUrl, project.name, moduleName));
return assignment.replace(/[`'"]/g, '');
}
export function createApiDefinitionReader(targetPath: string) {
return (tree: Tree) => {
try {
const buffer = tree.read(targetPath);
const apiDefinition: ApiDefinition = JSON.parse(buffer!.toString());
return apiDefinition;
} catch (_) {}
throw new SchematicsException(interpolate(Exception.NoApiDefinition, targetPath));
};
}
export function createApiDefinitionSaver(apiDefinition: ApiDefinition, targetPath: string) {
return (tree: Tree) => {
tree[tree.exists(targetPath) ? 'overwrite' : 'create'](
targetPath,
JSON.stringify(apiDefinition, null, 2),
);
};
}

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

@ -0,0 +1,15 @@
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) => strings.camelize(_(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, '/'));
function _(text: string): string {
return text.replace(/\./g, '_');
}

71
npm/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 || Boolean).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;

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

@ -0,0 +1,103 @@
import { strings } from '@angular-devkit/core';
import { SYSTEM_TYPES } from '../constants';
import { VOLO_REGEX } from '../constants/volo';
import { eImportKeyword } from '../enums';
import { Import, TypeWithEnum } from '../models';
import { parseNamespace } from './namespace';
import { relativePathToEnum, relativePathToModel } from './path';
import { parseGenerics } from './tree';
export function createTypeSimplifier() {
const parseType = createTypeParser(type => {
type = type.replace(
/System\.([0-9A-Za-z.]+)/g,
(_, match) => SYSTEM_TYPES.get(match) ?? strings.camelize(match),
);
return type.split('.').pop()!;
});
return (type: string) => parseType(type).join(' | ');
}
export function createTypeParser(replacerFn = (t: string) => t) {
const normalizeType = createTypeNormalizer(replacerFn);
return (originalType: string) => flattenUnionTypes([], originalType).map(normalizeType);
}
export function createTypeNormalizer(replacerFn = (t: string) => t) {
return (type: string) => {
type = normalizeTypeAnnotations(type);
return replacerFn(type);
};
}
export function flattenUnionTypes(types: string[], type: string) {
type
.replace(/^{/, '')
.replace(/}$/, '')
.replace(/{/, '(')
.replace(/}/, ')')
.split(':')
.filter(Boolean)
.forEach(t => types.push(t));
return types;
}
export function normalizeTypeAnnotations(type: string) {
type = type.replace(/\[(.+)+\]/g, '$1[]');
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();
}

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

@ -0,0 +1,67 @@
import { experimental, strings, workspaces } from '@angular-devkit/core';
import { SchematicsException, Tree } from '@angular-devkit/schematics';
import { Exception } from '../enums';
import { Project } from '../models';
import { getWorkspace, ProjectType } 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) {
const workspaceBuffer = tree.read('/angular.json') || tree.read('/workspace.json');
if (!workspaceBuffer) throw new SchematicsException(Exception.NoWorkspace);
let workspaceSchema: experimental.workspace.WorkspaceSchema;
try {
workspaceSchema = JSON.parse(workspaceBuffer.toString());
} 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/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"]
}

13
npm/ng-packs/packages/schematics/tslint.json

@ -0,0 +1,13 @@
{
"extends": "../../tslint.json",
"linterOptions": {
"exclude": ["**/utils/angular/**/*"]
},
"rules": {
"no-non-null-assertion": false,
"no-string-literal": false,
"variable-name": {
"options": ["allow-leading-underscore", "ban-keywords", "check-format", "allow-pascal-case"]
}
}
}

63
npm/ng-packs/scripts/build-schematics.ts

@ -0,0 +1,63 @@
import execa from 'execa';
import fse from 'fs-extra';
class FileCopy {
src: string;
dest: string;
options?: fse.CopyOptions;
constructor(filecopyOrSrc: FileCopy | string) {
if (typeof filecopyOrSrc === 'string') {
this.src = filecopyOrSrc;
this.dest = filecopyOrSrc;
} else {
this.src = filecopyOrSrc.src;
this.dest = filecopyOrSrc.dest;
this.options = filecopyOrSrc.options;
}
}
}
const PACKAGE_TO_BUILD = 'schematics';
const FILES_TO_COPY_AFTER_BUILD: (FileCopy | string)[] = [
{ src: 'src/commands/proxy/schema.json', dest: 'commands/proxy/schema.json' },
{ src: 'src/commands/api/files-enum', dest: 'commands/api/files-enum' },
{ src: 'src/commands/api/files-model', dest: 'commands/api/files-model' },
{ src: 'src/commands/api/files-service', dest: 'commands/api/files-service' },
{ src: 'src/commands/api/schema.json', dest: 'commands/api/schema.json' },
{ src: 'src/collection.json', dest: 'collection.json' },
'package.json',
'README.md',
];
async function* copyPackageFile(packageName: string, filecopy: FileCopy | string) {
filecopy = new FileCopy(filecopy);
const { src, dest, options = { overwrite: true } } = filecopy;
await fse.copy(`../packages/${packageName}/${src}`, `../dist/${packageName}/${dest}`, options);
yield filecopy;
}
async function* copyPackageFiles(packageName: string) {
for (const filecopy of FILES_TO_COPY_AFTER_BUILD) {
yield* copyPackageFile(packageName, filecopy);
}
}
(async () => {
await fse.remove(`../dist/${PACKAGE_TO_BUILD}`);
await execa(
'tsc',
['-p', `packages/${PACKAGE_TO_BUILD}/tsconfig.json`, '--outDir', `dist/${PACKAGE_TO_BUILD}`],
{
stdout: 'inherit',
cwd: '../',
},
);
for await (const filecopy of copyPackageFiles(PACKAGE_TO_BUILD)) {
// do nothing
}
})();

2
npm/ng-packs/scripts/build.ts

@ -38,7 +38,7 @@ import fse from 'fs-extra';
'--no-watch',
'--all-packages',
'--excluded-packages',
'@abp/ng.core,@abp/ng.theme.shared,@abp/ng.components,@abp/ng.feature-management,@abp/ng.permission-management',
'@abp/ng.schematics,@abp/ng.core,@abp/ng.theme.shared,@abp/ng.components,@abp/ng.feature-management,@abp/ng.permission-management',
],
{ stdout: 'inherit', cwd: '../' },
);

1
npm/ng-packs/scripts/package.json

@ -6,6 +6,7 @@
"scripts": {
"build": "ts-node -r tsconfig-paths/register build.ts",
"build:prod": "ts-node -r tsconfig-paths/register prod-build.ts",
"build:schematics": "ts-node -r tsconfig-paths/register build-schematics.ts",
"publish-packages": "ts-node -r tsconfig-paths/register publish.ts",
"replace-with-tilde": "ts-node -r tsconfig-paths/register replace-with-tilde.ts"
},

142
npm/ng-packs/yarn.lock

@ -2549,7 +2549,7 @@
estree-walker "^1.0.1"
picomatch "^2.2.2"
"@schematics/angular@10.0.6":
"@schematics/angular@10.0.6", "@schematics/angular@~10.0.5":
version "10.0.6"
resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-10.0.6.tgz#568590574dca1556280a0a04f625f79bee34ee4a"
integrity sha512-TPBpo0GnMJLvKE6rYZDkSy9pnkMH55rSJ6nfLDpQ5zzmhoD/QnASUr8trfTFs3+MqmPlX61xI00+HmStmI8sJQ==
@ -2582,6 +2582,11 @@
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==
"@sindresorhus/is@^3.0.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-3.1.0.tgz#d8735532635bea69ad39119df5f0f10099bd09dc"
integrity sha512-n4J+zu52VdY43kdi/XdI9DzuMr1Mur8zFL5ZRG2opCans9aiFwkPxHYFEb5Xgy7n1Z4K6WfI4FpqUqsh3E8BPQ==
"@sinonjs/commons@^1.7.0":
version "1.8.1"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217"
@ -2601,6 +2606,13 @@
dependencies:
defer-to-connect "^1.0.1"
"@szmarczak/http-timer@^4.0.5":
version "4.0.5"
resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.5.tgz#bfbd50211e9dfa51ba07da58a14cdfd333205152"
integrity sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==
dependencies:
defer-to-connect "^2.0.0"
"@testing-library/dom@6.1.0":
version "6.1.0"
resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-6.1.0.tgz#8d5a954158e81ecd7c994907f4ec240296ed823b"
@ -2646,6 +2658,16 @@
dependencies:
"@babel/types" "^7.3.0"
"@types/cacheable-request@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.1.tgz#5d22f3dded1fd3a84c0bbeb5039a7419c2c91976"
integrity sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==
dependencies:
"@types/http-cache-semantics" "*"
"@types/keyv" "*"
"@types/node" "*"
"@types/responselike" "*"
"@types/color-name@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
@ -2676,6 +2698,11 @@
dependencies:
"@types/node" "*"
"@types/http-cache-semantics@*":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a"
integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
@ -2729,6 +2756,13 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.5.tgz#dcce4430e64b443ba8945f0290fb564ad5bac6dd"
integrity sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==
"@types/keyv@*":
version "3.1.1"
resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.1.tgz#e45a45324fca9dab716ab1230ee249c9fb52cfa7"
integrity sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==
dependencies:
"@types/node" "*"
"@types/minimatch@*":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
@ -2786,6 +2820,13 @@
dependencies:
"@types/node" "*"
"@types/responselike@*", "@types/responselike@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29"
integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==
dependencies:
"@types/node" "*"
"@types/selenium-webdriver@^3.0.0":
version "3.0.17"
resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-3.0.17.tgz#50bea0c3c2acc31c959c5b1e747798b3b3d06d4b"
@ -4009,6 +4050,11 @@ cache-base@^1.0.1:
union-value "^1.0.0"
unset-value "^1.0.0"
cacheable-lookup@^5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.3.tgz#049fdc59dffdd4fc285e8f4f82936591bd59fec3"
integrity sha512-W+JBqF9SWe18A72XFzN/V/CULFzPm7sBXzzR6ekkE+3tLG72wFZrBiBZhrZuDoYexop4PHJVdFAKb/Nj9+tm9w==
cacheable-request@^6.0.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912"
@ -4022,6 +4068,19 @@ cacheable-request@^6.0.0:
normalize-url "^4.1.0"
responselike "^1.0.2"
cacheable-request@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.1.tgz#062031c2856232782ed694a257fa35da93942a58"
integrity sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==
dependencies:
clone-response "^1.0.2"
get-stream "^5.1.0"
http-cache-semantics "^4.0.0"
keyv "^4.0.0"
lowercase-keys "^2.0.0"
normalize-url "^4.1.0"
responselike "^2.0.0"
cachedir@2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.2.0.tgz#19afa4305e05d79e417566882e0c8f960f62ff0e"
@ -5354,6 +5413,13 @@ decompress-response@^3.3.0:
dependencies:
mimic-response "^1.0.0"
decompress-response@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc"
integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==
dependencies:
mimic-response "^3.1.0"
dedent@0.7.0, dedent@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
@ -5406,6 +5472,11 @@ defer-to-connect@^1.0.1:
resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.3.tgz#331ae050c08dcf789f8c83a7b81f0ed94f4ac591"
integrity sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==
defer-to-connect@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.0.tgz#83d6b199db041593ac84d781b5222308ccf4c2c1"
integrity sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg==
define-properties@^1.1.2, define-properties@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
@ -6786,6 +6857,23 @@ globby@^9.2.0:
pify "^4.0.1"
slash "^2.0.0"
got@^11.5.2:
version "11.5.2"
resolved "https://registry.yarnpkg.com/got/-/got-11.5.2.tgz#772e3f3a06d9c7589c7c94dc3c83cdb31ddbf742"
integrity sha512-yUhpEDLeuGiGJjRSzEq3kvt4zJtAcjKmhIiwNp/eUs75tRlXfWcHo5tcBaMQtnjHWC7nQYT5HkY/l0QOQTkVww==
dependencies:
"@sindresorhus/is" "^3.0.0"
"@szmarczak/http-timer" "^4.0.5"
"@types/cacheable-request" "^6.0.1"
"@types/responselike" "^1.0.0"
cacheable-lookup "^5.0.3"
cacheable-request "^7.0.1"
decompress-response "^6.0.0"
http2-wrapper "^1.0.0-beta.5.0"
lowercase-keys "^2.0.0"
p-cancelable "^2.0.0"
responselike "^2.0.0"
got@^9.6.0:
version "9.6.0"
resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85"
@ -7098,6 +7186,14 @@ http-signature@~1.2.0:
jsprim "^1.2.2"
sshpk "^1.7.0"
http2-wrapper@^1.0.0-beta.5.0:
version "1.0.0-beta.5.2"
resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.0-beta.5.2.tgz#8b923deb90144aea65cf834b016a340fc98556f3"
integrity sha512-xYz9goEyBnC8XwXDTuC/MZ6t+MrKVQZOk4s7+PaDkwIsQd8IwqvM+0M6bA/2lvG8GHXcPdf+MejTUeO2LCPCeQ==
dependencies:
quick-lru "^5.1.1"
resolve-alpn "^1.0.0"
https-browserify@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
@ -8389,6 +8485,11 @@ json-buffer@3.0.0:
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=
json-buffer@3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
json-parse-better-errors@^1.0.0, json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
@ -8428,6 +8529,11 @@ json5@^1.0.1:
dependencies:
minimist "^1.2.0"
jsonc-parser@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.3.0.tgz#7c7fc988ee1486d35734faaaa866fadb00fa91ee"
integrity sha512-b0EBt8SWFNnixVdvoR2ZtEGa9ZqLhbJnOjezn+WP+8kspFm+PFYDN8Z4Bc7pRlDjvuVcADSUkroIuTWWn/YiIA==
jsonfile@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
@ -8493,6 +8599,13 @@ keyv@^3.0.0:
dependencies:
json-buffer "3.0.0"
keyv@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.1.tgz#9fe703cb4a94d6d11729d320af033307efd02ee6"
integrity sha512-xz6Jv6oNkbhrFCvCP7HQa8AaII8y8LRpoSm661NOKLr4uHuBwhX4epXrPQgF3+xdJnN4Esm5X0xwY4bOlALOtw==
dependencies:
json-buffer "3.0.1"
killable@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
@ -9139,6 +9252,11 @@ mimic-response@^1.0.0, mimic-response@^1.0.1:
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
mimic-response@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
min-indent@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
@ -10001,6 +10119,11 @@ p-cancelable@^1.0.0:
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc"
integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==
p-cancelable@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e"
integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg==
p-each-series@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.1.0.tgz#961c8dd3f195ea96c747e636b262b800a6b1af48"
@ -11084,6 +11207,11 @@ quick-lru@^4.0.1:
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
quick-lru@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
@ -11510,6 +11638,11 @@ resize-observer-polyfill@^1.5.1:
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
resolve-alpn@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.0.0.tgz#745ad60b3d6aff4b4a48e01b8c0bdc70959e0e8c"
integrity sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA==
resolve-cwd@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
@ -11594,6 +11727,13 @@ responselike@^1.0.2:
dependencies:
lowercase-keys "^1.0.0"
responselike@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.0.tgz#26391bcc3174f750f9a79eacc40a12a5c42d7723"
integrity sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==
dependencies:
lowercase-keys "^2.0.0"
restore-cursor@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"

Loading…
Cancel
Save