Browse Source

Merge pull request #13716 from abpframework/feat/13293

Add create lib command
pull/14064/head
Mahmut Gundogdu 4 years ago
committed by GitHub
parent
commit
4c252b5ab2
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      npm/ng-packs/packages/schematics/package.json
  2. 5
      npm/ng-packs/packages/schematics/src/collection.json
  3. 4
      npm/ng-packs/packages/schematics/src/commands/api/index.ts
  4. 9
      npm/ng-packs/packages/schematics/src/commands/api/schema.json
  5. 44
      npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/.eslintrc.json.template
  6. 7
      npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/config/ng-package.json.template
  7. 12
      npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/config/src/__libraryName@kebab__-config.module.ts.template
  8. 1
      npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/config/src/enums/index.ts.template
  9. 3
      npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/config/src/enums/route-names.ts.template
  10. 1
      npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/config/src/providers/index.ts.template
  11. 26
      npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/config/src/providers/route.provider.ts.template
  12. 3
      npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/config/src/public-api.ts.template
  13. 44
      npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/karma.conf.js.template
  14. 7
      npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/ng-package.json.template
  15. 11
      npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/package.json.template
  16. 18
      npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/src/lib/__libraryName@kebab__-routing.module.ts.template
  17. 22
      npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/src/lib/__libraryName@kebab__.module.ts.template
  18. 4
      npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/src/public-api.ts.template
  19. 26
      npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/src/test.ts.template
  20. 20
      npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/tsconfig.lib.json.template
  21. 10
      npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/tsconfig.lib.prod.json.template
  22. 17
      npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/tsconfig.spec.json.template
  23. 7
      npm/ng-packs/packages/schematics/src/commands/create-lib/files-secondary-entrypoint/__libraryName@kebab__/ng-package.json.template
  24. 11
      npm/ng-packs/packages/schematics/src/commands/create-lib/files-secondary-entrypoint/__libraryName@kebab__/src/lib/__target@kebab__-__libraryName@kebab__.module.ts.template
  25. 1
      npm/ng-packs/packages/schematics/src/commands/create-lib/files-secondary-entrypoint/__libraryName@kebab__/src/lib/index.ts.template
  26. 1
      npm/ng-packs/packages/schematics/src/commands/create-lib/files-secondary-entrypoint/__libraryName@kebab__/src/public-api.ts.template
  27. 159
      npm/ng-packs/packages/schematics/src/commands/create-lib/index.ts
  28. 17
      npm/ng-packs/packages/schematics/src/commands/create-lib/models/generate-lib-schema.ts
  29. 1
      npm/ng-packs/packages/schematics/src/commands/create-lib/models/index.ts
  30. 36
      npm/ng-packs/packages/schematics/src/commands/create-lib/schema.json
  31. 4
      npm/ng-packs/packages/schematics/src/commands/proxy-add/index.ts
  32. 9
      npm/ng-packs/packages/schematics/src/commands/proxy-add/schema.json
  33. 6
      npm/ng-packs/packages/schematics/src/commands/proxy-index/index.ts
  34. 9
      npm/ng-packs/packages/schematics/src/commands/proxy-index/schema.json
  35. 4
      npm/ng-packs/packages/schematics/src/commands/proxy-refresh/index.ts
  36. 10
      npm/ng-packs/packages/schematics/src/commands/proxy-refresh/schema.json
  37. 4
      npm/ng-packs/packages/schematics/src/commands/proxy-remove/index.ts
  38. 9
      npm/ng-packs/packages/schematics/src/commands/proxy-remove/schema.json
  39. 1
      npm/ng-packs/packages/schematics/src/constants/symbols.ts
  40. 1
      npm/ng-packs/packages/schematics/src/enums/exception.ts
  41. 5
      npm/ng-packs/packages/schematics/src/models/generate-proxy-schema.ts
  42. 52
      npm/ng-packs/packages/schematics/src/utils/angular-schematic/generate-lib.ts
  43. 1
      npm/ng-packs/packages/schematics/src/utils/angular-schematic/index.ts
  44. 2
      npm/ng-packs/packages/schematics/src/utils/angular/README.md
  45. 193
      npm/ng-packs/packages/schematics/src/utils/angular/ast-utils.ts
  46. 32
      npm/ng-packs/packages/schematics/src/utils/angular/change.ts
  47. 541
      npm/ng-packs/packages/schematics/src/utils/angular/config.ts
  48. 12
      npm/ng-packs/packages/schematics/src/utils/angular/dependencies.ts
  49. 25
      npm/ng-packs/packages/schematics/src/utils/angular/find-module.ts
  50. 61
      npm/ng-packs/packages/schematics/src/utils/angular/generate-from-files.ts
  51. 6
      npm/ng-packs/packages/schematics/src/utils/angular/index.ts
  52. 43
      npm/ng-packs/packages/schematics/src/utils/angular/json-file.ts
  53. 221
      npm/ng-packs/packages/schematics/src/utils/angular/json-utils.ts
  54. 26
      npm/ng-packs/packages/schematics/src/utils/angular/latest-versions.ts
  55. 20
      npm/ng-packs/packages/schematics/src/utils/angular/latest-versions/package.json
  56. 54
      npm/ng-packs/packages/schematics/src/utils/angular/lint-fix.ts
  57. 9
      npm/ng-packs/packages/schematics/src/utils/angular/ng-ast-utils.ts
  58. 5
      npm/ng-packs/packages/schematics/src/utils/angular/parse-name.ts
  59. 2
      npm/ng-packs/packages/schematics/src/utils/angular/paths.ts
  60. 2
      npm/ng-packs/packages/schematics/src/utils/angular/project-targets.ts
  61. 76
      npm/ng-packs/packages/schematics/src/utils/angular/tsconfig.ts
  62. 60
      npm/ng-packs/packages/schematics/src/utils/angular/validation.ts
  63. 23
      npm/ng-packs/packages/schematics/src/utils/angular/workspace-models.ts
  64. 51
      npm/ng-packs/packages/schematics/src/utils/angular/workspace.ts
  65. 1
      npm/ng-packs/packages/schematics/src/utils/index.ts
  66. 9
      npm/ng-packs/packages/schematics/src/utils/lib.ts
  67. 2
      npm/ng-packs/packages/schematics/src/utils/source.ts
  68. 49
      npm/ng-packs/packages/schematics/src/utils/workspace.ts
  69. 6
      npm/ng-packs/scripts/build-schematics.ts

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

@ -9,12 +9,12 @@
"license": "MIT", "license": "MIT",
"schematics": "./collection.json", "schematics": "./collection.json",
"dependencies": { "dependencies": {
"@angular-devkit/core": "~11.0.2", "@angular-devkit/core": "~13.3.9",
"@angular-devkit/schematics": "~11.0.2", "@angular-devkit/schematics": "~13.3.9",
"got": "^11.5.2", "got": "^11.5.2",
"jsonc-parser": "^2.3.0", "jsonc-parser": "^2.3.0",
"should-quote": "^1.0.0", "should-quote": "^1.0.0",
"typescript": "~3.9.2" "typescript": "~4.6.4"
}, },
"devDependencies": { "devDependencies": {
"@schematics/angular": "~11.0.2", "@schematics/angular": "~11.0.2",

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

@ -24,6 +24,11 @@
"description": "ABP API Generator Schematics", "description": "ABP API Generator Schematics",
"factory": "./commands/api", "factory": "./commands/api",
"schema": "./commands/api/schema.json" "schema": "./commands/api/schema.json"
},
"create-lib": {
"description": "ABP Create Library Schematics",
"factory": "./commands/create-lib",
"schema": "./commands/create-lib/schema.json"
} }
} }
} }

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

@ -13,7 +13,7 @@ import { Exception } from '../../enums';
import { GenerateProxySchema, ServiceGeneratorParams } from '../../models'; import { GenerateProxySchema, ServiceGeneratorParams } from '../../models';
import { import {
applyWithOverwrite, applyWithOverwrite,
buildDefaultPath, buildTargetPath,
createControllerToServiceMapper, createControllerToServiceMapper,
createImportRefsToModelReducer, createImportRefsToModelReducer,
createImportRefToEnumMapper, createImportRefToEnumMapper,
@ -42,7 +42,7 @@ export default function (schema: GenerateProxySchema) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const target = await resolveProject(tree, params.target!); const target = await resolveProject(tree, params.target!);
const targetPath = buildDefaultPath(target.definition); const targetPath = buildTargetPath(target.definition, params.entryPoint);
const readProxyConfig = createProxyConfigReader(targetPath); const readProxyConfig = createProxyConfigReader(targetPath);
const createProxyConfigWriter = createProxyConfigWriterCreator(targetPath); const createProxyConfigWriter = createProxyConfigWriterCreator(targetPath);
const data = readProxyConfig(tree); const data = readProxyConfig(tree);

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

@ -39,6 +39,15 @@
"index": 3 "index": 3
}, },
"x-prompt": "Please enter target Angular project to place the generated code. (default: workspace \"defaultProject\")" "x-prompt": "Please enter target Angular project to place the generated code. (default: workspace \"defaultProject\")"
},
"entryPoint": {
"description": "Target Angular project to place the generated code",
"type": "string",
"$default": {
"$source": "argv",
"index": 5
},
"x-prompt": "Please enter target Angular project to place the generated code. (default: workspace \"defaultProject\")"
} }
}, },
"required": [] "required": []

44
npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/.eslintrc.json.template

@ -0,0 +1,44 @@
{
"extends": "../../.eslintrc.json",
"ignorePatterns": [
"!**/*"
],
"overrides": [
{
"files": [
"*.ts"
],
"parserOptions": {
"project": [
"projects/<%= kebab(libraryName) %>/tsconfig.lib.json",
"projects/<%= kebab(libraryName) %>/tsconfig.spec.json"
],
"createDefaultProgram": true
},
"rules": {
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "lib",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "lib",
"style": "kebab-case"
}
]
}
},
{
"files": [
"*.html"
],
"rules": {}
}
]
}

7
npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/config/ng-package.json.template

@ -0,0 +1,7 @@
{
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/<%= kebab(libraryName) %>/config",
"lib": {
"entryFile": "src/public-api.ts"
}
}

12
npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/config/src/__libraryName@kebab__-config.module.ts.template

@ -0,0 +1,12 @@
import { ModuleWithProviders, NgModule } from '@angular/core';
import { <%= macro(libraryName) %>_ROUTE_PROVIDERS } from './providers/route.provider';
@NgModule()
export class <%= pascal(libraryName) %>ConfigModule {
static forRoot(): ModuleWithProviders<<%= pascal(libraryName) %>ConfigModule> {
return {
ngModule: <%= pascal(libraryName) %>ConfigModule,
providers: [<%= macro(libraryName) %>_ROUTE_PROVIDERS],
};
}
}

1
npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/config/src/enums/index.ts.template

@ -0,0 +1 @@
export * from './route-names';

3
npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/config/src/enums/route-names.ts.template

@ -0,0 +1,3 @@
export const enum e<%= pascal(libraryName) %>RouteNames {
<%= pascal(libraryName) %> = '<%= pascal(libraryName) %>',
}

1
npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/config/src/providers/index.ts.template

@ -0,0 +1 @@
export * from './route.provider';

26
npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/config/src/providers/route.provider.ts.template

@ -0,0 +1,26 @@
import { eLayoutType, RoutesService } from '@abp/ng.core';
import { APP_INITIALIZER } from '@angular/core';
import { e<%= pascal(libraryName) %>RouteNames } from '../enums/route-names';
export const <%= macro(libraryName) %>_ROUTE_PROVIDERS = [
{
provide: APP_INITIALIZER,
useFactory: configureRoutes,
deps: [RoutesService],
multi: true,
},
];
export function configureRoutes(routesService: RoutesService) {
return () => {
routesService.add([
{
path: '/<%= kebab(libraryName) %>',
name: e<%= pascal(libraryName) %>RouteNames.<%= pascal(libraryName) %>,
iconClass: 'fas fa-book',
layout: eLayoutType.application,
order: 3,
},
]);
};
}

3
npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/config/src/public-api.ts.template

@ -0,0 +1,3 @@
export * from './enums';
export * from './<%= kebab(libraryName) %>-config.module';
export * from './providers';

44
npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/karma.conf.js.template

@ -0,0 +1,44 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true // removes the duplicated traces
},
coverageReporter: {
dir: require('path').join(__dirname, '../../coverage/my-project-name'),
subdir: '.',
reporters: [
{ type: 'html' },
{ type: 'text-summary' }
]
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};

7
npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/ng-package.json.template

@ -0,0 +1,7 @@
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/<%= kebab(libraryName) %>",
"lib": {
"entryFile": "src/public-api.ts"
}
}

11
npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/package.json.template

@ -0,0 +1,11 @@
{
"name": "@<%= kebab(libraryName) %>/<%= kebab(libraryName) %>",
"version": "0.0.1",
"peerDependencies": {
"@abp/ng.core": "<%= abpVersion %>",
"@abp/ng.theme.shared": "<%= abpVersion %>"
},
"dependencies": {
"tslib": "^2.1.0"
}
}

18
npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/src/lib/__libraryName@kebab__-routing.module.ts.template

@ -0,0 +1,18 @@
import { NgModule } from '@angular/core';
import { DynamicLayoutComponent } from '@abp/ng.core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [
{
path: '',
pathMatch: 'full',
component: DynamicLayoutComponent,
children: [],
},
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
})
export class <%= pascal(libraryName) %>RoutingModule {}

22
npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/src/lib/__libraryName@kebab__.module.ts.template

@ -0,0 +1,22 @@
import { NgModule, NgModuleFactory, ModuleWithProviders } from '@angular/core';
import { CoreModule, LazyModuleFactory } from '@abp/ng.core';
import { ThemeSharedModule } from '@abp/ng.theme.shared';
import { <%= pascal(libraryName) %>RoutingModule } from './<%= kebab(libraryName) %>-routing.module';
@NgModule({
declarations: [],
imports: [CoreModule, ThemeSharedModule, <%= pascal(libraryName) %>RoutingModule],
exports: [],
})
export class <%= pascal(libraryName) %>Module {
static forChild(): ModuleWithProviders<<%= pascal(libraryName) %>Module> {
return {
ngModule: <%= pascal(libraryName) %>Module,
providers: [],
};
}
static forLazy(): NgModuleFactory<<%= pascal(libraryName) %>Module> {
return new LazyModuleFactory(<%= pascal(libraryName) %>Module.forChild());
}
}

4
npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/src/public-api.ts.template

@ -0,0 +1,4 @@
/*
* Public API Surface of my-project-name
*/
export * from './lib/<%= kebab(libraryName) %>.module';

26
npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/src/test.ts.template

@ -0,0 +1,26 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js';
import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: {
context(path: string, deep?: boolean, filter?: RegExp): {
keys(): string[];
<T>(id: string): T;
};
};
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

20
npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/tsconfig.lib.json.template

@ -0,0 +1,20 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/lib",
"target": "es2015",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": [],
"lib": [
"dom",
"es2018"
]
},
"exclude": [
"src/test.ts",
"**/*.spec.ts"
]
}

10
npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/tsconfig.lib.prod.json.template

@ -0,0 +1,10 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "./tsconfig.lib.json",
"compilerOptions": {
"declarationMap": false
},
"angularCompilerOptions": {
"compilationMode": "partial"
}
}

17
npm/ng-packs/packages/schematics/src/commands/create-lib/files-package/__libraryName@kebab__/tsconfig.spec.json.template

@ -0,0 +1,17 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../out-tsc/spec",
"types": [
"jasmine"
]
},
"files": [
"src/test.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

7
npm/ng-packs/packages/schematics/src/commands/create-lib/files-secondary-entrypoint/__libraryName@kebab__/ng-package.json.template

@ -0,0 +1,7 @@
{
"$schema": "../../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/<%= kebab(target) %>/<%= kebab(libraryName) %>",
"lib": {
"entryFile": "src/public-api.ts"
}
}

11
npm/ng-packs/packages/schematics/src/commands/create-lib/files-secondary-entrypoint/__libraryName@kebab__/src/lib/__target@kebab__-__libraryName@kebab__.module.ts.template

@ -0,0 +1,11 @@
import { ModuleWithProviders, NgModule } from '@angular/core';
@NgModule()
export class <%= pascal(target) %><%= pascal(libraryName) %>Module {
static forRoot(): ModuleWithProviders<<%= pascal(target) %><%= pascal(libraryName) %>Module> {
return {
ngModule:<%= pascal(target) %><%= pascal(libraryName) %>Module,
providers: []
}
}
}

1
npm/ng-packs/packages/schematics/src/commands/create-lib/files-secondary-entrypoint/__libraryName@kebab__/src/lib/index.ts.template

@ -0,0 +1 @@
export * from './<%= kebab(target) %>-<%= kebab(libraryName) %>.module';

1
npm/ng-packs/packages/schematics/src/commands/create-lib/files-secondary-entrypoint/__libraryName@kebab__/src/public-api.ts.template

@ -0,0 +1 @@
export * from './lib/index';

159
npm/ng-packs/packages/schematics/src/commands/create-lib/index.ts

@ -0,0 +1,159 @@
import {
applyTemplates,
chain,
move,
noop,
Rule,
SchematicContext,
SchematicsException,
Tree,
url,
} from '@angular-devkit/schematics';
import { GenerateLibSchema } from './models/generate-lib-schema';
import {
applyWithOverwrite,
getWorkspace,
interpolate,
isLibrary,
JSONFile,
kebab,
resolveProject,
updateWorkspace,
} from '../../utils';
import * as cases from '../../utils/text';
import { Exception } from '../../enums';
import { join, normalize } from '@angular-devkit/core';
import {
ProjectDefinition,
WorkspaceDefinition,
} from '@angular-devkit/core/src/workspace/definitions';
import { addLibToWorkspaceFile } from '../../utils/angular-schematic/generate-lib';
export default function (schema: GenerateLibSchema) {
return async (tree: Tree) => {
if (schema.override || !(await checkLibExist(schema, tree))) {
return chain([createLibrary(schema)]);
}
};
}
async function checkLibExist(options: GenerateLibSchema, tree: Tree) {
const packageName = kebab(options.packageName);
if (options.isSecondaryEntrypoint) {
const lib = await resolveProject(tree, options.target);
const ngPackagePath = `${lib?.definition.root}/${packageName}/ng-package.json`;
const packageInfo = tree.read(ngPackagePath);
if (packageInfo) {
throw new SchematicsException(
interpolate(Exception.LibraryAlreadyExists, `${lib.name}/${packageName}`),
);
}
return false;
}
const target = await resolveProject(tree, options.packageName, null);
if (target) {
throw new SchematicsException(interpolate(Exception.LibraryAlreadyExists, packageName));
}
return false;
}
function createLibrary(options: GenerateLibSchema): Rule {
return async (tree: Tree, _context: SchematicContext) => {
const target = await resolveProject(tree, options.packageName, null);
if (!target || options.override) {
if (options.isModuleTemplate) {
return createLibFromModuleTemplate(tree, options);
}
if (options.isSecondaryEntrypoint) {
return createLibSecondaryEntry(tree, options);
}
} else {
throw new SchematicsException(
interpolate(Exception.LibraryAlreadyExists, options.packageName),
);
}
};
}
async function resolvePackagesDirFromAngularJson(host: Tree) {
const workspace = await getWorkspace(host);
const projectFolder = readFirstLibInAngularJson(workspace);
return projectFolder?.root?.split('/')?.[0] || 'projects';
}
function readFirstLibInAngularJson(workspace: WorkspaceDefinition): ProjectDefinition | undefined {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_, props] =
Array.from(workspace.projects.entries()).find(([_, value]) => isLibrary(value)) || [];
return props;
}
async function createLibFromModuleTemplate(tree: Tree, options: GenerateLibSchema) {
const packagesDir = await resolvePackagesDirFromAngularJson(tree);
const packageJson = JSON.parse(tree.read('./package.json')!.toString());
const abpVersion = packageJson.dependencies['@abp/ng.core'];
return chain([
applyWithOverwrite(url('./files-package'), [
applyTemplates({
...cases,
libraryName: options.packageName,
abpVersion,
}),
move(normalize(packagesDir)),
]),
addLibToWorkspaceIfNotExist(options.packageName, packagesDir),
]);
}
export function addLibToWorkspaceIfNotExist(name: string, packagesDir: string): Rule {
return async (tree: Tree, _: SchematicContext) => {
const workspace = await getWorkspace(tree);
const packageName = kebab(name);
const isProjectExist = workspace.projects.has(packageName);
const projectRoot = join(normalize(packagesDir), packageName);
const pathImportLib = `${packagesDir}/${packageName}`;
return chain([
isProjectExist
? updateWorkspace(w => {
w.projects.delete(packageName);
})
: noop(),
addLibToWorkspaceFile(projectRoot, packageName),
updateTsConfig(packageName, pathImportLib),
]);
};
}
export function updateTsConfig(packageName: string, path: string) {
return (host: Tree) => {
const files = ['tsconfig.json', 'tsconfig.app.json', 'tsconfig.base.json'];
const tsConfig = files.find(f => host.exists(f));
if (!tsConfig) {
return host;
}
const file = new JSONFile(host, tsConfig);
const jsonPath = ['compilerOptions', 'paths', packageName];
file.modify(jsonPath, [`${path}/src/public-api.ts`]);
};
}
export async function createLibSecondaryEntry(tree: Tree, options: GenerateLibSchema) {
const targetLib = await resolveProject(tree, options.target);
const packageName = `${kebab(targetLib.name)}/${kebab(options.packageName)}`;
const importPath = `${targetLib.definition.root}/${kebab(options.packageName)}`;
return chain([
applyWithOverwrite(url('./files-secondary-entrypoint'), [
applyTemplates({
...cases,
libraryName: options.packageName,
target: targetLib.name,
}),
move(normalize(targetLib.definition.root)),
updateTsConfig(packageName, importPath),
]),
]);
}

17
npm/ng-packs/packages/schematics/src/commands/create-lib/models/generate-lib-schema.ts

@ -0,0 +1,17 @@
export interface GenerateLibSchema {
/**
* Angular package name will create
*/
packageName: string;
/**
* İs the package a library or a library module
*/
isSecondaryEntrypoint: boolean;
isModuleTemplate: boolean;
override: boolean;
target: string;
}

1
npm/ng-packs/packages/schematics/src/commands/create-lib/models/index.ts

@ -0,0 +1 @@
export * from './generate-lib-schema';

36
npm/ng-packs/packages/schematics/src/commands/create-lib/schema.json

@ -0,0 +1,36 @@
{
"$schema": "http://json-schema.org/schema",
"$id": "SchematicsABPModuleTemplateCreator",
"title": "ABP Module Template Generator API Schema",
"type": "object",
"properties": {
"packageName": {
"description": "The name of the package will create",
"type": "string",
"$default": {
"$source": "argv",
"index": 0
},
"x-prompt": "Please enter the package name will create"
},
"isSecondaryEntrypoint": {
"description": "Is secondary entrypoint",
"type": "boolean",
"$default": false,
"x-prompt": "Is secondary entrypoint?"
},
"isModuleTemplate": {
"description": "Is module template",
"type": "boolean",
"$default": true,
"x-prompt": "Is module template?"
},
"override": {
"description": "Override existing files",
"type": "boolean",
"$default": false,
"x-prompt": "Override existing files?"
}
},
"required": []
}

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

@ -1,7 +1,7 @@
import { chain, SchematicContext, Tree } from '@angular-devkit/schematics'; import { chain, SchematicContext, Tree } from '@angular-devkit/schematics';
import { GenerateProxySchema } from '../../models'; import { GenerateProxySchema } from '../../models';
import { import {
buildDefaultPath, buildTargetPath,
createApiDefinitionGetter, createApiDefinitionGetter,
createApisGenerator, createApisGenerator,
createProxyClearer, createProxyClearer,
@ -21,7 +21,7 @@ export default function (schema: GenerateProxySchema) {
return chain([ return chain([
async (host: Tree, _context: SchematicContext) => { async (host: Tree, _context: SchematicContext) => {
const target = await resolveProject(host, params.target!); const target = await resolveProject(host, params.target!);
const targetPath = buildDefaultPath(target.definition); const targetPath = buildTargetPath(target.definition, params.entryPoint);
const readProxyConfig = createProxyConfigReader(targetPath); const readProxyConfig = createProxyConfigReader(targetPath);
let generated: string[] = []; let generated: string[] = [];

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

@ -50,5 +50,14 @@
"x-prompt": "Please enter URL for API definition (default: API Name's url in environment file)" "x-prompt": "Please enter URL for API definition (default: API Name's url in environment file)"
} }
}, },
"entryPoint": {
"description": "Target Angular project to place the generated code",
"type": "string",
"$default": {
"$source": "argv",
"index": 5
},
"x-prompt": "Please enter target Angular project to place the generated code. (default: workspace \"defaultProject\")"
},
"required": [] "required": []
} }

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

@ -1,18 +1,18 @@
import { SchematicContext, Tree } from '@angular-devkit/schematics'; import { SchematicContext, Tree } from '@angular-devkit/schematics';
import { import {
buildDefaultPath, buildTargetPath,
createProxyIndexGenerator, createProxyIndexGenerator,
removeDefaultPlaceholders, removeDefaultPlaceholders,
resolveProject, resolveProject,
} from '../../utils'; } from '../../utils';
export default function (schema: { target?: string }) { export default function (schema: { target?: string; entryPoint?: string }) {
const params = removeDefaultPlaceholders(schema); const params = removeDefaultPlaceholders(schema);
return async (host: Tree, _context: SchematicContext) => { return async (host: Tree, _context: SchematicContext) => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const target = await resolveProject(host, params.target!); const target = await resolveProject(host, params.target!);
const targetPath = buildDefaultPath(target.definition); const targetPath = buildTargetPath(target.definition, params.entryPoint);
const generateIndex = createProxyIndexGenerator(targetPath); const generateIndex = createProxyIndexGenerator(targetPath);

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

@ -12,6 +12,15 @@
"index": 0 "index": 0
}, },
"x-prompt": "Please enter target Angular project to place the generated code. (default: workspace \"defaultProject\")" "x-prompt": "Please enter target Angular project to place the generated code. (default: workspace \"defaultProject\")"
},
"entryPoint": {
"description": "Target Angular project to place the generated code",
"type": "string",
"$default": {
"$source": "argv",
"index": 5
},
"x-prompt": "Please enter target Angular project to place the generated code. (default: workspace \"defaultProject\")"
} }
}, },
"required": [] "required": []

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

@ -1,7 +1,7 @@
import { chain, SchematicContext, Tree } from '@angular-devkit/schematics'; import { chain, SchematicContext, Tree } from '@angular-devkit/schematics';
import { GenerateProxySchema } from '../../models'; import { GenerateProxySchema } from '../../models';
import { import {
buildDefaultPath, buildTargetPath,
createApiDefinitionGetter, createApiDefinitionGetter,
createApisGenerator, createApisGenerator,
createProxyClearer, createProxyClearer,
@ -18,7 +18,7 @@ export default function (schema: GenerateProxySchema) {
return async (host: Tree, _context: SchematicContext) => { return async (host: Tree, _context: SchematicContext) => {
const target = await resolveProject(host, params.target!); const target = await resolveProject(host, params.target!);
const targetPath = buildDefaultPath(target.definition); const targetPath = buildTargetPath(target.definition, params.entryPoint);
const readProxyConfig = createProxyConfigReader(targetPath); const readProxyConfig = createProxyConfigReader(targetPath);
const { generated } = readProxyConfig(host); const { generated } = readProxyConfig(host);

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

@ -48,7 +48,17 @@
"index": 4 "index": 4
}, },
"x-prompt": "Please enter URL for api definition (default: API Name's url in environment file)" "x-prompt": "Please enter URL for api definition (default: API Name's url in environment file)"
},
"entryPoint": {
"description": "Target Angular project to place the generated code",
"type": "string",
"$default": {
"$source": "argv",
"index": 5
},
"x-prompt": "Please enter target Angular project to place the generated code. (default: workspace \"defaultProject\")"
} }
}, },
"required": [] "required": []
} }

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

@ -1,7 +1,7 @@
import { chain, SchematicContext, Tree } from '@angular-devkit/schematics'; import { chain, SchematicContext, Tree } from '@angular-devkit/schematics';
import { GenerateProxySchema } from '../../models'; import { GenerateProxySchema } from '../../models';
import { import {
buildDefaultPath, buildTargetPath,
createApiDefinitionGetter, createApiDefinitionGetter,
createApisGenerator, createApisGenerator,
createProxyClearer, createProxyClearer,
@ -20,7 +20,7 @@ export default function (schema: GenerateProxySchema) {
return async (host: Tree, _context: SchematicContext) => { return async (host: Tree, _context: SchematicContext) => {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const target = await resolveProject(host, params.target!); const target = await resolveProject(host, params.target!);
const targetPath = buildDefaultPath(target.definition); const targetPath = buildTargetPath(target.definition, params.entryPoint);
const readProxyConfig = createProxyConfigReader(targetPath); const readProxyConfig = createProxyConfigReader(targetPath);
const { generated } = readProxyConfig(host); const { generated } = readProxyConfig(host);

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

@ -48,6 +48,15 @@
"index": 4 "index": 4
}, },
"x-prompt": "Please enter URL for API definition (default: API Name's url in environment file)" "x-prompt": "Please enter URL for API definition (default: API Name's url in environment file)"
},
"entryPoint": {
"description": "Target Angular project to place the generated code",
"type": "string",
"$default": {
"$source": "argv",
"index": 5
},
"x-prompt": "Please enter target Angular project to place the generated code. (default: workspace \"defaultProject\")"
} }
}, },
"required": [] "required": []

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

@ -0,0 +1 @@
export const NOT_FOUND_VALUE = Symbol('NOT_FOUND_VALUE');

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

@ -13,4 +13,5 @@ export const enum Exception {
NoEnvironment = '[Environment Not Found] An environment file cannot be located in "{0}" project.', NoEnvironment = '[Environment Not Found] An environment file cannot be located in "{0}" project.',
NoApiUrl = '[API URL Not Found] Cannot resolve API URL for "{1}" remote service name from "{0}" project.', NoApiUrl = '[API URL Not Found] Cannot resolve API URL for "{1}" remote service name from "{0}" project.',
NoRootNamespace = '[Root Namespace Not Found] Cannot resolve root namespace for "{1}" api from "{0}" project.', NoRootNamespace = '[Root Namespace Not Found] Cannot resolve root namespace for "{1}" api from "{0}" project.',
LibraryAlreadyExists = '[Library Already Exists] "{0}" already exists. Use --override to override.',
} }

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

@ -23,4 +23,9 @@ export interface GenerateProxySchema {
* Url to API definition * Url to API definition
*/ */
url?: string; url?: string;
/**
* Secondary entrypoint for proxy generation
*/
entryPoint?: string;
} }

52
npm/ng-packs/packages/schematics/src/utils/angular-schematic/generate-lib.ts

@ -0,0 +1,52 @@
import { Rule, Tree } from '@angular-devkit/schematics';
import { Builders, JSONFile, ProjectType, updateWorkspace } from '../angular';
export function updateTsConfig(packageName: string, ...paths: string[]) {
return (host: Tree) => {
if (!host.exists('tsconfig.json')) {
return host;
}
const file = new JSONFile(host, 'tsconfig.json');
const jsonPath = ['compilerOptions', 'paths', packageName];
const value = file.get(jsonPath);
file.modify(jsonPath, Array.isArray(value) ? [...value, ...paths] : paths);
};
}
export function addLibToWorkspaceFile(projectRoot: string, projectName: string): Rule {
return updateWorkspace(workspace => {
workspace.projects.add({
name: projectName,
root: projectRoot,
sourceRoot: `${projectRoot}/src`,
projectType: ProjectType.Library,
prefix: 'lib',
targets: {
build: {
builder: Builders.NgPackagr,
defaultConfiguration: 'production',
options: {
project: `${projectRoot}/ng-package.json`,
},
configurations: {
production: {
tsConfig: `${projectRoot}/tsconfig.lib.prod.json`,
},
development: {
tsConfig: `${projectRoot}/tsconfig.lib.json`,
},
},
},
test: {
builder: Builders.Karma,
options: {
main: `${projectRoot}/src/test.ts`,
tsConfig: `${projectRoot}/tsconfig.spec.json`,
karmaConfig: `${projectRoot}/karma.conf.js`,
},
},
},
});
});
}

1
npm/ng-packs/packages/schematics/src/utils/angular-schematic/index.ts

@ -0,0 +1 @@
export * from './generate-lib';

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

@ -1,5 +1,5 @@
**DISCLAIMER** **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. This directory is a direct copy of https://github.com/angular/angular-cli/tree/d091bb0c7b76a73e1b7d4b5690c532c68452b1d6 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. All credits go to Angular team for building these utilities.

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

@ -1,10 +1,12 @@
/** /**
* @license * @license
* Copyright Google Inc. All Rights Reserved. * Copyright Google LLC All Rights Reserved.
* *
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import { tags } from '@angular-devkit/core';
import * as ts from 'typescript'; import * as ts from 'typescript';
import { Change, InsertChange, NoopChange } from './change'; import { Change, InsertChange, NoopChange } from './change';
@ -146,7 +148,7 @@ export function findNodes<T extends ts.Node>(
} }
if (max > 0 && (recursive || !test(node))) { if (max > 0 && (recursive || !test(node))) {
for (const child of node.getChildren()) { for (const child of node.getChildren()) {
findNodes(child, test, max).forEach(node => { findNodes(child, test, max, recursive).forEach(node => {
if (max > 0) { if (max > 0) {
arr.push(node); arr.push(node);
} }
@ -187,7 +189,6 @@ export function getSourceNodes(sourceFile: ts.SourceFile): ts.Node[] {
export function findNode(node: ts.Node, kind: ts.SyntaxKind, text: string): ts.Node | null { export function findNode(node: ts.Node, kind: ts.SyntaxKind, text: string): ts.Node | null {
if (node.kind === kind && node.getText() === text) { if (node.kind === kind && node.getText() === text) {
// throw new Error(node.getText());
return node; return node;
} }
@ -244,20 +245,7 @@ export function insertAfterLastOccurrence(
return new InsertChange(file, lastItemPosition, toInsert); return new InsertChange(file, lastItemPosition, toInsert);
} }
export function getContentOfKeyLiteral(_source: ts.SourceFile, node: ts.Node): string | null { function _angularImportsFromNode(node: ts.ImportDeclaration): { [name: string]: string } {
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; const ms = node.moduleSpecifier;
let modulePath: string; let modulePath: string;
switch (ms.kind) { switch (ms.kind) {
@ -281,11 +269,11 @@ function _angularImportsFromNode(
if (nb.kind == ts.SyntaxKind.NamespaceImport) { if (nb.kind == ts.SyntaxKind.NamespaceImport) {
// This is of the form `import * as name from 'path'`. Return `name.`. // This is of the form `import * as name from 'path'`. Return `name.`.
return { return {
[(nb as ts.NamespaceImport).name.text + '.']: modulePath, [nb.name.text + '.']: modulePath,
}; };
} else { } else {
// This is of the form `import {a,b,c} from 'path'` // This is of the form `import {a,b,c} from 'path'`
const namedImports = nb as ts.NamedImports; const namedImports = nb;
return namedImports.elements return namedImports.elements
.map((is: ts.ImportSpecifier) => (is.propertyName ? is.propertyName.text : is.name.text)) .map((is: ts.ImportSpecifier) => (is.propertyName ? is.propertyName.text : is.name.text))
@ -310,7 +298,7 @@ export function getDecoratorMetadata(
module: string, module: string,
): ts.Node[] { ): ts.Node[] {
const angularImports = findNodes(source, ts.isImportDeclaration) const angularImports = findNodes(source, ts.isImportDeclaration)
.map(node => _angularImportsFromNode(node, source)) .map(node => _angularImportsFromNode(node))
.reduce((acc, current) => { .reduce((acc, current) => {
for (const key of Object.keys(current)) { for (const key of Object.keys(current)) {
acc[key] = current[key]; acc[key] = current[key];
@ -354,38 +342,6 @@ export function getDecoratorMetadata(
.map(expr => expr.arguments[0] as ts.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( export function getMetadataField(
node: ts.ObjectLiteralExpression, node: ts.ObjectLiteralExpression,
metadataField: string, metadataField: string,
@ -396,9 +352,7 @@ export function getMetadataField(
// Filter out every fields that's not "metadataField". Also handles string literals // Filter out every fields that's not "metadataField". Also handles string literals
// (but not expressions). // (but not expressions).
.filter(({ name }) => { .filter(({ name }) => {
return ( return (ts.isIdentifier(name) || ts.isStringLiteral(name)) && name.text === metadataField;
(ts.isIdentifier(name) || ts.isStringLiteral(name)) && name.getText() === metadataField
);
}) })
); );
} }
@ -411,36 +365,33 @@ export function addSymbolToNgModuleMetadata(
importPath: string | null = null, importPath: string | null = null,
): Change[] { ): Change[] {
const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core'); const nodes = getDecoratorMetadata(source, 'NgModule', '@angular/core');
let node: any = nodes[0]; const node = nodes[0];
// Find the decorator declaration. // Find the decorator declaration.
if (!node) { if (!node || !ts.isObjectLiteralExpression(node)) {
return []; return [];
} }
// Get all the children property assignment of object literals. // Get all the children property assignment of object literals.
const matchingProperties = getMetadataField(node as ts.ObjectLiteralExpression, metadataField); const matchingProperties = getMetadataField(node, metadataField);
// Get the last node of the array literal.
if (!matchingProperties) {
return [];
}
if (matchingProperties.length == 0) { if (matchingProperties.length == 0) {
// We haven't found the field in the metadata declaration. Insert a new field. // We haven't found the field in the metadata declaration. Insert a new field.
const expr = node as ts.ObjectLiteralExpression;
let position: number; let position: number;
let toInsert: string; let toInsert: string;
if (expr.properties.length == 0) { if (node.properties.length == 0) {
position = expr.getEnd() - 1; position = node.getEnd() - 1;
toInsert = ` ${metadataField}: [${symbolName}]\n`; toInsert = `\n ${metadataField}: [\n${tags.indentBy(4)`${symbolName}`}\n ]\n`;
} else { } else {
node = expr.properties[expr.properties.length - 1]; const childNode = node.properties[node.properties.length - 1];
position = node.getEnd(); position = childNode.getEnd();
// Get the indentation of the last element, if any. // Get the indentation of the last element, if any.
const text = node.getFullText(source); const text = childNode.getFullText(source);
const matches = text.match(/^\r?\n\s*/); const matches = text.match(/^(\r?\n)(\s*)/);
if (matches && matches.length > 0) { if (matches) {
toInsert = `,${matches[0]}${metadataField}: [${symbolName}]`; toInsert =
`,${matches[0]}${metadataField}: [${matches[1]}` +
`${tags.indentBy(matches[2].length + 2)`${symbolName}`}${matches[0]}]`;
} else { } else {
toInsert = `, ${metadataField}: [${symbolName}]`; toInsert = `, ${metadataField}: [${symbolName}]`;
} }
@ -454,69 +405,48 @@ export function addSymbolToNgModuleMetadata(
return [new InsertChange(ngModulePath, position, toInsert)]; return [new InsertChange(ngModulePath, position, toInsert)];
} }
} }
const assignment = matchingProperties[0] as ts.PropertyAssignment; const assignment = matchingProperties[0];
// If it's not an array, nothing we can do really. // If it's not an array, nothing we can do really.
if (assignment.initializer.kind !== ts.SyntaxKind.ArrayLiteralExpression) { if (
!ts.isPropertyAssignment(assignment) ||
!ts.isArrayLiteralExpression(assignment.initializer)
) {
return []; return [];
} }
const arrLiteral = assignment.initializer as ts.ArrayLiteralExpression; let expresssion: ts.Expression | ts.ArrayLiteralExpression;
if (arrLiteral.elements.length == 0) { const assignmentInit = assignment.initializer;
// Forward the property. const elements = assignmentInit.elements;
node = arrLiteral;
} else {
node = arrLiteral.elements;
}
if (!node) {
console.error('No app module found. Please add your new class to your component.');
return []; if (elements.length) {
} const symbolsArray = elements.map(node => tags.oneLine`${node.getText()}`);
if (symbolsArray.includes(tags.oneLine`${symbolName}`)) {
if (Array.isArray(node)) {
// eslint-disable-next-line @typescript-eslint/ban-types
const nodeArray = node as {} as Array<ts.Node>;
const symbolsArray = nodeArray.map(node => node.getText());
if (symbolsArray.includes(symbolName)) {
return []; return [];
} }
node = node[node.length - 1]; expresssion = elements[elements.length - 1];
} else {
expresssion = assignmentInit;
} }
let toInsert: string; let toInsert: string;
let position = node.getEnd(); let position = expresssion.getEnd();
if (node.kind == ts.SyntaxKind.ObjectLiteralExpression) { if (ts.isArrayLiteralExpression(expresssion)) {
// 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 `]`. // We found the field but it's empty. Insert it just before the `]`.
position--; position--;
toInsert = `${symbolName}`; toInsert = `\n${tags.indentBy(4)`${symbolName}`}\n `;
} else { } else {
// Get the indentation of the last element, if any. // Get the indentation of the last element, if any.
const text = node.getFullText(source); const text = expresssion.getFullText(source);
if (text.match(/^\r?\n/)) { const matches = text.match(/^(\r?\n)(\s*)/);
toInsert = `,${text.match(/^\r?\n(\r?)\s*/)[0]}${symbolName}`; if (matches) {
toInsert = `,${matches[1]}${tags.indentBy(matches[2].length)`${symbolName}`}`;
} else { } else {
toInsert = `, ${symbolName}`; toInsert = `, ${symbolName}`;
} }
} }
if (importPath !== null) { if (importPath !== null) {
return [ return [
new InsertChange(ngModulePath, position, toInsert), new InsertChange(ngModulePath, position, toInsert),
@ -594,25 +524,6 @@ export function addBootstrapToModule(
return addSymbolToNgModuleMetadata(source, modulePath, 'bootstrap', classifiedName, importPath); 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. * Determine if an import already exists.
*/ */
@ -690,10 +601,13 @@ export function getEnvironmentExportName(source: ts.SourceFile): string | null {
* Returns the RouterModule declaration from NgModule metadata, if any. * Returns the RouterModule declaration from NgModule metadata, if any.
*/ */
export function getRouterModuleDeclaration(source: ts.SourceFile): ts.Expression | undefined { export function getRouterModuleDeclaration(source: ts.SourceFile): ts.Expression | undefined {
const result = getDecoratorMetadata(source, 'NgModule', '@angular/core') as ts.Node[]; const result = getDecoratorMetadata(source, 'NgModule', '@angular/core');
const node = result[0] as ts.ObjectLiteralExpression; const node = result[0];
const matchingProperties = getMetadataField(node, 'imports'); if (!node || !ts.isObjectLiteralExpression(node)) {
return undefined;
}
const matchingProperties = getMetadataField(node, 'imports');
if (!matchingProperties) { if (!matchingProperties) {
return; return;
} }
@ -721,7 +635,10 @@ export function addRouteDeclarationToModule(
): Change { ): Change {
const routerModuleExpr = getRouterModuleDeclaration(source); const routerModuleExpr = getRouterModuleDeclaration(source);
if (!routerModuleExpr) { if (!routerModuleExpr) {
throw new Error(`Couldn't find a route declaration in ${fileToAdd}.`); throw new Error(
`Couldn't find a route declaration in ${fileToAdd}.\n` +
`Use the '--module' option to specify a different routing module.`,
);
} }
const scopeConfigMethodArgs = (routerModuleExpr as ts.CallExpression).arguments; const scopeConfigMethodArgs = (routerModuleExpr as ts.CallExpression).arguments;
if (!scopeConfigMethodArgs.length) { if (!scopeConfigMethodArgs.length) {

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

@ -1,10 +1,13 @@
/** /**
* @license * @license
* Copyright Google Inc. All Rights Reserved. * Copyright Google LLC All Rights Reserved.
* *
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import { UpdateRecorder } from '@angular-devkit/schematics';
export interface Host { export interface Host {
write(path: string, content: string): Promise<void>; write(path: string, content: string): Promise<void>;
read(path: string): Promise<string>; read(path: string): Promise<string>;
@ -56,7 +59,7 @@ export class InsertChange implements Change {
* This method does not insert spaces if there is none in the original string. * This method does not insert spaces if there is none in the original string.
*/ */
apply(host: Host) { apply(host: Host) {
return host.read(this.path).then(content => { return host.read(this.path).then((content) => {
const prefix = content.substring(0, this.pos); const prefix = content.substring(0, this.pos);
const suffix = content.substring(this.pos); const suffix = content.substring(this.pos);
@ -72,7 +75,7 @@ export class RemoveChange implements Change {
order: number; order: number;
description: string; description: string;
constructor(public path: string, private pos: number, private toRemove: string) { constructor(public path: string, private pos: number, public toRemove: string) {
if (pos < 0) { if (pos < 0) {
throw new Error('Negative positions are invalid'); throw new Error('Negative positions are invalid');
} }
@ -81,7 +84,7 @@ export class RemoveChange implements Change {
} }
apply(host: Host): Promise<void> { apply(host: Host): Promise<void> {
return host.read(this.path).then(content => { return host.read(this.path).then((content) => {
const prefix = content.substring(0, this.pos); const prefix = content.substring(0, this.pos);
const suffix = content.substring(this.pos + this.toRemove.length); const suffix = content.substring(this.pos + this.toRemove.length);
@ -101,8 +104,8 @@ export class ReplaceChange implements Change {
constructor( constructor(
public path: string, public path: string,
private pos: number, private pos: number,
private oldText: string, public oldText: string,
private newText: string, public newText: string,
) { ) {
if (pos < 0) { if (pos < 0) {
throw new Error('Negative positions are invalid'); throw new Error('Negative positions are invalid');
@ -112,7 +115,7 @@ export class ReplaceChange implements Change {
} }
apply(host: Host): Promise<void> { apply(host: Host): Promise<void> {
return host.read(this.path).then(content => { return host.read(this.path).then((content) => {
const prefix = content.substring(0, this.pos); const prefix = content.substring(0, this.pos);
const suffix = content.substring(this.pos + this.oldText.length); const suffix = content.substring(this.pos + this.oldText.length);
const text = content.substring(this.pos, this.pos + this.oldText.length); const text = content.substring(this.pos, this.pos + this.oldText.length);
@ -126,3 +129,18 @@ export class ReplaceChange implements Change {
}); });
} }
} }
export function applyToUpdateRecorder(recorder: UpdateRecorder, changes: Change[]): void {
for (const change of changes) {
if (change instanceof InsertChange) {
recorder.insertLeft(change.pos, change.toAdd);
} else if (change instanceof RemoveChange) {
recorder.remove(change.order, change.toRemove.length);
} else if (change instanceof ReplaceChange) {
recorder.remove(change.order, change.oldText.length);
recorder.insertLeft(change.order, change.newText);
} else if (!(change instanceof NoopChange)) {
throw new Error('Unknown Change type encountered when updating a recorder.');
}
}
}

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

@ -1,541 +0,0 @@
/* eslint-disable @typescript-eslint/ban-types */
/**
* @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;
}
)[];
/**
* 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;
}
)[];
/**
* Source file for environment config.
*/
environmentSource?: string;
/**
* Name and corresponding file for environment config.
*/
environments?: {
[name: string]: 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];
}

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

@ -1,10 +1,11 @@
/** /**
* @license * @license
* Copyright Google Inc. All Rights Reserved. * Copyright Google LLC All Rights Reserved.
* *
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import { Tree } from '@angular-devkit/schematics'; import { Tree } from '@angular-devkit/schematics';
import { JSONFile } from './json-file'; import { JSONFile } from './json-file';
@ -36,9 +37,6 @@ export function addPackageJsonDependency(
pkgJsonPath = PKG_JSON_PATH, pkgJsonPath = PKG_JSON_PATH,
): void { ): void {
const json = new JSONFile(tree, pkgJsonPath); const json = new JSONFile(tree, pkgJsonPath);
if (json.error) {
throw json.error;
}
const { overwrite, type, name, version } = dependency; const { overwrite, type, name, version } = dependency;
const path = [type, name]; const path = [type, name];
@ -53,9 +51,6 @@ export function removePackageJsonDependency(
pkgJsonPath = PKG_JSON_PATH, pkgJsonPath = PKG_JSON_PATH,
): void { ): void {
const json = new JSONFile(tree, pkgJsonPath); const json = new JSONFile(tree, pkgJsonPath);
if (json.error) {
throw json.error;
}
for (const depType of ALL_DEPENDENCY_TYPE) { for (const depType of ALL_DEPENDENCY_TYPE) {
json.remove([depType, name]); json.remove([depType, name]);
@ -68,9 +63,6 @@ export function getPackageJsonDependency(
pkgJsonPath = PKG_JSON_PATH, pkgJsonPath = PKG_JSON_PATH,
): NodeDependency | null { ): NodeDependency | null {
const json = new JSONFile(tree, pkgJsonPath); const json = new JSONFile(tree, pkgJsonPath);
if (json.error) {
throw json.error;
}
for (const depType of ALL_DEPENDENCY_TYPE) { for (const depType of ALL_DEPENDENCY_TYPE) {
const version = json.get([depType, name]); const version = json.get([depType, name]);

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

@ -1,21 +1,19 @@
/** /**
* @license * @license
* Copyright Google Inc. All Rights Reserved. * Copyright Google LLC All Rights Reserved.
* *
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import { dirname, join, normalize, NormalizedRoot, Path, relative } from '@angular-devkit/core';
import { NormalizedRoot, Path, dirname, join, normalize, relative } from '@angular-devkit/core';
import { DirEntry, Tree } from '@angular-devkit/schematics'; import { DirEntry, Tree } from '@angular-devkit/schematics';
export interface ModuleOptions { export interface ModuleOptions {
project?: string; // added this
module?: string; module?: string;
name: string; name: string;
flat?: boolean; flat?: boolean;
path?: string; path?: string;
route?: string; // added this
selector?: string; // added this
skipImport?: boolean; skipImport?: boolean;
moduleExt?: string; moduleExt?: string;
routingModuleExt?: string; routingModuleExt?: string;
@ -28,7 +26,8 @@ export const ROUTING_MODULE_EXT = '-routing.module.ts';
* Find the module referred by a set of options passed to the schematics. * Find the module referred by a set of options passed to the schematics.
*/ */
export function findModuleFromOptions(host: Tree, options: ModuleOptions): Path | undefined { export function findModuleFromOptions(host: Tree, options: ModuleOptions): Path | undefined {
if (Object.prototype.hasOwnProperty.call(options, 'skipImport') && options.skipImport) { // eslint-disable-next-line no-prototype-builtins
if (options.hasOwnProperty('skipImport') && options.skipImport) {
return undefined; return undefined;
} }
@ -55,8 +54,8 @@ export function findModuleFromOptions(host: Tree, options: ModuleOptions): Path
const candidatesDirs = [...candidateSet].sort((a, b) => b.length - a.length); const candidatesDirs = [...candidateSet].sort((a, b) => b.length - a.length);
for (const c of candidatesDirs) { for (const c of candidatesDirs) {
const candidateFiles = ['', `${moduleBaseName}.ts`, `${moduleBaseName}${moduleExt}`].map(x => const candidateFiles = ['', `${moduleBaseName}.ts`, `${moduleBaseName}${moduleExt}`].map(
join(c, x), (x) => join(c, x),
); );
for (const sc of candidateFiles) { for (const sc of candidateFiles) {
@ -86,8 +85,8 @@ export function findModule(
let foundRoutingModule = false; let foundRoutingModule = false;
while (dir) { while (dir) {
const allMatches = dir.subfiles.filter(p => p.endsWith(moduleExt)); const allMatches = dir.subfiles.filter((p) => p.endsWith(moduleExt));
const filteredMatches = allMatches.filter(p => !p.endsWith(routingModuleExt)); const filteredMatches = allMatches.filter((p) => !p.endsWith(routingModuleExt));
foundRoutingModule = foundRoutingModule || allMatches.length !== filteredMatches.length; foundRoutingModule = foundRoutingModule || allMatches.length !== filteredMatches.length;
@ -95,7 +94,7 @@ export function findModule(
return join(dir.path, filteredMatches[0]); return join(dir.path, filteredMatches[0]);
} else if (filteredMatches.length > 1) { } else if (filteredMatches.length > 1) {
throw new Error( throw new Error(
'More than one module matches. Use the skip-import option to skip importing ' + `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.', 'the component into the closest module or use the module option to specify a module.',
); );
} }
@ -106,8 +105,8 @@ export function findModule(
const errorMsg = foundRoutingModule const errorMsg = foundRoutingModule
? 'Could not find a non Routing NgModule.' + ? 'Could not find a non Routing NgModule.' +
`\nModules with suffix '${routingModuleExt}' are strictly reserved for routing.` + `\nModules with suffix '${routingModuleExt}' are strictly reserved for routing.` +
'\nUse the skip-import option to skip importing in NgModule.' `\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.'; : `Could not find an NgModule. Use the '--skip-import' option to skip importing in NgModule.`;
throw new Error(errorMsg); throw new Error(errorMsg);
} }

61
npm/ng-packs/packages/schematics/src/utils/angular/generate-from-files.ts

@ -0,0 +1,61 @@
/**
* @license
* Copyright Google LLC 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 { strings } from '@angular-devkit/core';
import {
apply,
applyTemplates,
chain,
filter,
mergeWith,
move,
noop,
Rule,
Tree,
url,
} from '@angular-devkit/schematics';
import { parseName } from './parse-name';
import { createDefaultPath } from './workspace';
export interface GenerateFromFilesOptions {
flat?: boolean;
name: string;
path?: string;
prefix?: string;
project?: string;
skipTests?: boolean;
}
export function generateFromFiles(
options: GenerateFromFilesOptions,
extraTemplateValues: Record<string, string | ((v: string) => string)> = {},
): Rule {
return async (host: Tree) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
options.path ??= await createDefaultPath(host, options.project as string);
options.prefix ??= '';
options.flat ??= true;
const parsedPath = parseName(options.path!, options.name);
options.name = parsedPath.name;
options.path = parsedPath.path;
const templateSource = apply(url('./files'), [
options.skipTests ? filter(path => !path.endsWith('.spec.ts.template')) : noop(),
applyTemplates({
...strings,
...options,
...extraTemplateValues,
}),
move(parsedPath.path + (options.flat ? '' : '/' + strings.dasherize(options.name))),
]);
return chain([mergeWith(templateSource)]);
};
}

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

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

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

@ -1,6 +1,6 @@
/** /**
* @license * @license
* Copyright Google Inc. All Rights Reserved. * Copyright Google LLC All Rights Reserved.
* *
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
@ -10,46 +10,62 @@ import { JsonValue } from '@angular-devkit/core';
import { Tree } from '@angular-devkit/schematics'; import { Tree } from '@angular-devkit/schematics';
import { import {
Node, Node,
ParseError,
applyEdits, applyEdits,
findNodeAtLocation, findNodeAtLocation,
getNodeValue, getNodeValue,
modify, modify,
parseTree, parseTree,
printParseErrorCode,
} from 'jsonc-parser'; } from 'jsonc-parser';
export type InsertionIndex = (properties: string[]) => number;
export type JSONPath = (string | number)[]; export type JSONPath = (string | number)[];
/** @internal */ /** @internal */
export class JSONFile { export class JSONFile {
private content: string; content: string;
error: undefined | Error;
constructor(private readonly host: Tree, private readonly path: string) { constructor(private readonly host: Tree, private readonly path: string) {
const buffer = this.host.read(this.path); const buffer = this.host.read(this.path);
if (buffer) { if (buffer) {
this.content = buffer.toString(); this.content = buffer.toString();
} else { } else {
this.error = new Error(`Could not read ${path}.`); throw new Error(`Could not read '${path}'.`);
} }
} }
private _jsonAst: Node | undefined; private _jsonAst: Node | undefined;
private get JsonAst(): Node { private get JsonAst(): Node | undefined {
if (this._jsonAst) { if (this._jsonAst) {
return this._jsonAst; return this._jsonAst;
} }
this._jsonAst = parseTree(this.content); const errors: ParseError[] = [];
this._jsonAst = parseTree(this.content, errors, { allowTrailingComma: true });
if (errors.length) {
const { error, offset } = errors[0];
throw new Error(
`Failed to parse "${this.path}" as JSON AST Object. ${printParseErrorCode(
error,
)} at location: ${offset}.`,
);
}
return this._jsonAst; return this._jsonAst;
} }
get(jsonPath: JSONPath): unknown { get(jsonPath: JSONPath): unknown {
const jsonAstNode = this.JsonAst;
if (!jsonAstNode) {
return undefined;
}
if (jsonPath.length === 0) { if (jsonPath.length === 0) {
return getNodeValue(this.JsonAst); return getNodeValue(jsonAstNode);
} }
const node = findNodeAtLocation(this.JsonAst, jsonPath); const node = findNodeAtLocation(jsonAstNode, jsonPath);
return node === undefined ? undefined : getNodeValue(node); return node === undefined ? undefined : getNodeValue(node);
} }
@ -57,12 +73,15 @@ export class JSONFile {
modify( modify(
jsonPath: JSONPath, jsonPath: JSONPath,
value: JsonValue | undefined, value: JsonValue | undefined,
getInsertionIndex?: (properties: string[]) => number, insertInOrder?: InsertionIndex | false,
): void { ): void {
if (!getInsertionIndex) { let getInsertionIndex: InsertionIndex | undefined;
if (insertInOrder === undefined) {
const property = jsonPath.slice(-1)[0]; const property = jsonPath.slice(-1)[0];
getInsertionIndex = properties => getInsertionIndex = (properties) =>
[...properties, property].sort().findIndex(p => p === property); [...properties, property].sort().findIndex((p) => p === property);
} else if (insertInOrder !== false) {
getInsertionIndex = insertInOrder;
} }
const edits = modify(this.content, jsonPath, value, { const edits = modify(this.content, jsonPath, value, {

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

@ -1,221 +0,0 @@
/**
* @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;
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

@ -1,26 +0,0 @@
/**
* @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',
};

20
npm/ng-packs/packages/schematics/src/utils/angular/latest-versions/package.json

@ -0,0 +1,20 @@
{
"description": "Package versions used by schematics in @schematics/angular.",
"comment": "This file is needed so that depedencies are synced by Renovate.",
"private": true,
"dependencies": {
"@types/jasmine": "~3.10.0",
"@types/node": "^12.11.1",
"jasmine-core": "~4.0.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.1.0",
"karma-jasmine-html-reporter": "~1.7.0",
"karma-jasmine": "~4.0.0",
"karma": "~6.3.0",
"ng-packagr": "^13.0.0",
"rxjs": "~6.6.7",
"tslib": "^2.3.0",
"typescript": "~4.6.2",
"zone.js": "~0.11.4"
}
}

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

@ -1,54 +0,0 @@
/**
* @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],
}),
);
};
}

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

@ -1,10 +1,11 @@
/** /**
* @license * @license
* Copyright Google Inc. All Rights Reserved. * Copyright Google LLC All Rights Reserved.
* *
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import { normalize } from '@angular-devkit/core'; import { normalize } from '@angular-devkit/core';
import { SchematicsException, Tree } from '@angular-devkit/schematics'; import { SchematicsException, Tree } from '@angular-devkit/schematics';
import { dirname } from 'path'; import { dirname } from 'path';
@ -59,17 +60,17 @@ export function findBootstrapModulePath(host: Tree, mainPath: string): string {
const mainBuffer = host.read(mainPath); const mainBuffer = host.read(mainPath);
if (!mainBuffer) { if (!mainBuffer) {
throw new SchematicsException(`Client app main file (${mainPath}) not found`); throw new SchematicsException(`Client application main file (${mainPath}) not found`);
} }
const mainText = mainBuffer.toString('utf-8'); const mainText = mainBuffer.toString('utf-8');
const source = ts.createSourceFile(mainPath, mainText, ts.ScriptTarget.Latest, true); const source = ts.createSourceFile(mainPath, mainText, ts.ScriptTarget.Latest, true);
const allNodes = getSourceNodes(source); const allNodes = getSourceNodes(source);
const bootstrapModuleRelativePath = allNodes const bootstrapModuleRelativePath = allNodes
.filter(node => node.kind === ts.SyntaxKind.ImportDeclaration) .filter(ts.isImportDeclaration)
.filter(imp => { .filter(imp => {
return findNode(imp, ts.SyntaxKind.Identifier, bootstrapModule.getText()); return findNode(imp, ts.SyntaxKind.Identifier, bootstrapModule.getText());
}) })
.map((imp: ts.ImportDeclaration) => { .map(imp => {
const modulePathStringLiteral = imp.moduleSpecifier as ts.StringLiteral; const modulePathStringLiteral = imp.moduleSpecifier as ts.StringLiteral;
return modulePathStringLiteral.text; return modulePathStringLiteral.text;

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

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

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

@ -1,6 +1,6 @@
/** /**
* @license * @license
* Copyright Google Inc. All Rights Reserved. * Copyright Google LLC All Rights Reserved.
* *
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license

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

@ -1,6 +1,6 @@
/** /**
* @license * @license
* Copyright Google Inc. All Rights Reserved. * Copyright Google LLC All Rights Reserved.
* *
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license

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

@ -1,76 +0,0 @@
/**
* @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.json')) {
return;
}
throw new SchematicsException(
`Cannot find base TypeScript configuration file 'tsconfig.json'.`,
);
}

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

@ -1,20 +1,14 @@
/** /**
* @license * @license
* Copyright Google Inc. All Rights Reserved. * Copyright Google LLC All Rights Reserved.
* *
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import { tags } from '@angular-devkit/core'; import { tags } from '@angular-devkit/core';
import { SchematicsException } from '@angular-devkit/schematics'; 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. // 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. // 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 const htmlSelectorRe = /^[a-zA-Z][.0-9a-zA-Z]*(:?-[a-zA-Z][.0-9a-zA-Z]*)*$/;
@ -25,53 +19,3 @@ export function validateHtmlSelector(selector: string): void {
is invalid.`); 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;
}

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

@ -1,11 +1,11 @@
/* eslint-disable @typescript-eslint/ban-types */
/** /**
* @license * @license
* Copyright Google Inc. All Rights Reserved. * Copyright Google LLC All Rights Reserved.
* *
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
export enum ProjectType { export enum ProjectType {
Application = 'application', Application = 'application',
Library = 'library', Library = 'library',
@ -49,7 +49,6 @@ export interface BrowserBuilderOptions extends BrowserBuilderBaseOptions {
optimization?: boolean; optimization?: boolean;
outputHashing?: OutputHashing; outputHashing?: OutputHashing;
resourcesOutputPath?: string; resourcesOutputPath?: string;
extractCss?: boolean;
namedChunks?: boolean; namedChunks?: boolean;
aot?: boolean; aot?: boolean;
extractLicenses?: boolean; extractLicenses?: boolean;
@ -77,10 +76,12 @@ export interface ServerBuilderOptions {
tsConfig: string; tsConfig: string;
main: string; main: string;
fileReplacements?: FileReplacements[]; fileReplacements?: FileReplacements[];
optimization?: { optimization?:
scripts?: boolean; | boolean
styles?: boolean; | {
}; scripts?: boolean;
styles?: boolean;
};
sourceMap?: sourceMap?:
| boolean | boolean
| { | {
@ -101,11 +102,6 @@ export interface TestBuilderOptions extends Partial<BrowserBuilderBaseOptions> {
karmaConfig: string; karmaConfig: string;
} }
export interface LintBuilderOptions {
tsConfig: string[] | string;
exclude?: string[];
}
export interface ExtractI18nOptions { export interface ExtractI18nOptions {
browserTarget: string; browserTarget: string;
} }
@ -128,7 +124,6 @@ export type LibraryBuilderTarget = BuilderTarget<Builders.NgPackagr, LibraryBuil
export type BrowserBuilderTarget = BuilderTarget<Builders.Browser, BrowserBuilderOptions>; export type BrowserBuilderTarget = BuilderTarget<Builders.Browser, BrowserBuilderOptions>;
export type ServerBuilderTarget = BuilderTarget<Builders.Server, ServerBuilderOptions>; export type ServerBuilderTarget = BuilderTarget<Builders.Server, ServerBuilderOptions>;
export type AppShellBuilderTarget = BuilderTarget<Builders.AppShell, AppShellBuilderOptions>; export type AppShellBuilderTarget = BuilderTarget<Builders.AppShell, AppShellBuilderOptions>;
export type LintBuilderTarget = BuilderTarget<Builders.TsLint, LintBuilderOptions>;
export type TestBuilderTarget = BuilderTarget<Builders.Karma, TestBuilderOptions>; export type TestBuilderTarget = BuilderTarget<Builders.Karma, TestBuilderOptions>;
export type ServeBuilderTarget = BuilderTarget<Builders.DevServer, ServeBuilderOptions>; export type ServeBuilderTarget = BuilderTarget<Builders.DevServer, ServeBuilderOptions>;
export type ExtractI18nBuilderTarget = BuilderTarget<Builders.ExtractI18n, ExtractI18nOptions>; export type ExtractI18nBuilderTarget = BuilderTarget<Builders.ExtractI18n, ExtractI18nOptions>;
@ -168,12 +163,12 @@ export interface WorkspaceProject<TProjectType extends ProjectType = ProjectType
export interface WorkspaceTargets<TProjectType extends ProjectType = ProjectType.Application> { export interface WorkspaceTargets<TProjectType extends ProjectType = ProjectType.Application> {
build?: TProjectType extends ProjectType.Library ? LibraryBuilderTarget : BrowserBuilderTarget; build?: TProjectType extends ProjectType.Library ? LibraryBuilderTarget : BrowserBuilderTarget;
server?: ServerBuilderTarget; server?: ServerBuilderTarget;
lint?: LintBuilderTarget;
test?: TestBuilderTarget; test?: TestBuilderTarget;
serve?: ServeBuilderTarget; serve?: ServeBuilderTarget;
e2e?: E2EBuilderTarget; e2e?: E2EBuilderTarget;
'app-shell'?: AppShellBuilderTarget; 'app-shell'?: AppShellBuilderTarget;
'extract-i18n'?: ExtractI18nBuilderTarget; 'extract-i18n'?: ExtractI18nBuilderTarget;
// TODO(hans): change this any to unknown when google3 supports TypeScript 3.0. // TODO(hans): change this any to unknown when google3 supports TypeScript 3.0.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any; [key: string]: any;
} }

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

@ -1,12 +1,13 @@
/** /**
* @license * @license
* Copyright Google Inc. All Rights Reserved. * Copyright Google LLC All Rights Reserved.
* *
* Use of this source code is governed by an MIT-style license that can be * 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 * 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 { json, virtualFs, workspaces } from '@angular-devkit/core';
import { noop, Rule, Tree } from '@angular-devkit/schematics';
import { ProjectType } from './workspace-models'; import { ProjectType } from './workspace-models';
function createHost(tree: Tree): workspaces.WorkspaceHost { function createHost(tree: Tree): workspaces.WorkspaceHost {
@ -33,13 +34,13 @@ function createHost(tree: Tree): workspaces.WorkspaceHost {
} }
export function updateWorkspace( export function updateWorkspace(
updater: (workspace: workspaces.WorkspaceDefinition) => void | PromiseLike<void>, updater: (workspace: workspaces.WorkspaceDefinition) => void | Rule | PromiseLike<void | Rule>,
): Rule; ): Rule;
export function updateWorkspace(workspace: workspaces.WorkspaceDefinition): Rule; export function updateWorkspace(workspace: workspaces.WorkspaceDefinition): Rule;
export function updateWorkspace( export function updateWorkspace(
updaterOrWorkspace: updaterOrWorkspace:
| workspaces.WorkspaceDefinition | workspaces.WorkspaceDefinition
| ((workspace: workspaces.WorkspaceDefinition) => void | PromiseLike<void>), | ((workspace: workspaces.WorkspaceDefinition) => void | Rule | PromiseLike<void | Rule>),
): Rule { ): Rule {
return async (tree: Tree) => { return async (tree: Tree) => {
const host = createHost(tree); const host = createHost(tree);
@ -47,14 +48,15 @@ export function updateWorkspace(
if (typeof updaterOrWorkspace === 'function') { if (typeof updaterOrWorkspace === 'function') {
const { workspace } = await workspaces.readWorkspace('/', host); const { workspace } = await workspaces.readWorkspace('/', host);
const result = updaterOrWorkspace(workspace); const result = await updaterOrWorkspace(workspace);
if (result !== undefined) {
await result;
}
await workspaces.writeWorkspace(workspace, host); await workspaces.writeWorkspace(workspace, host);
return result || noop;
} else { } else {
await workspaces.writeWorkspace(updaterOrWorkspace, host); await workspaces.writeWorkspace(updaterOrWorkspace, host);
return noop;
} }
}; };
} }
@ -83,8 +85,37 @@ export async function createDefaultPath(tree: Tree, projectName: string): Promis
const workspace = await getWorkspace(tree); const workspace = await getWorkspace(tree);
const project = workspace.projects.get(projectName); const project = workspace.projects.get(projectName);
if (!project) { if (!project) {
throw new Error('Specified project does not exist.'); throw new Error(`Project "${projectName}" does not exist.`);
} }
return buildDefaultPath(project); return buildDefaultPath(project);
} }
export function* allWorkspaceTargets(
workspace: workspaces.WorkspaceDefinition,
): Iterable<[string, workspaces.TargetDefinition, string, workspaces.ProjectDefinition]> {
for (const [projectName, project] of workspace.projects) {
for (const [targetName, target] of project.targets) {
yield [targetName, target, projectName, project];
}
}
}
export function* allTargetOptions(
target: workspaces.TargetDefinition,
skipBaseOptions = false,
): Iterable<[string | undefined, Record<string, json.JsonValue | undefined>]> {
if (!skipBaseOptions && target.options) {
yield [undefined, target.options];
}
if (!target.configurations) {
return;
}
for (const [name, options] of Object.entries(target.configurations)) {
if (options !== undefined) {
yield [name, options];
}
}
}

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

@ -7,6 +7,7 @@ export * from './enum';
export * from './file'; export * from './file';
export * from './generics'; export * from './generics';
export * from './import'; export * from './import';
export * from './lib';
export * from './methods'; export * from './methods';
export * from './model'; export * from './model';
export * from './namespace'; export * from './namespace';

9
npm/ng-packs/packages/schematics/src/utils/lib.ts

@ -0,0 +1,9 @@
import { Tree } from '@angular-devkit/schematics';
import { resolveProject } from "./workspace";
export async function createTargetLibIfNotExist(tree: Tree, target: string) {
const lib = await resolveProject(tree, target, null);
if (!lib) {
}
}

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

@ -23,7 +23,7 @@ export function createApiDefinitionGetter(params: GenerateProxySchema) {
if (!sourceUrl) { if (!sourceUrl) {
sourceUrl = getSourceUrl(host, source, apiName); sourceUrl = getSourceUrl(host, source, apiName);
} }
return await getApiDefinition(sourceUrl); return await getApiDefinition(sourceUrl!);
}; };
} }

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

@ -3,9 +3,11 @@ import { strings, workspaces } from '@angular-devkit/core';
import { SchematicsException, Tree } from '@angular-devkit/schematics'; import { SchematicsException, Tree } from '@angular-devkit/schematics';
import { Exception } from '../enums'; import { Exception } from '../enums';
import { Project } from '../models'; import { Project } from '../models';
import { getWorkspace, getWorkspaceSchema, ProjectType, WorkspaceSchema } from './angular'; import { buildDefaultPath, getWorkspace, ProjectType, WorkspaceSchema } from './angular';
import { findEnvironmentExpression } from './ast'; import { findEnvironmentExpression } from './ast';
import { readFileInTree } from './common'; import { readFileInTree } from './common';
import { NOT_FOUND_VALUE } from '../constants/symbols';
import { parseJson } from '@angular/cli/utilities/json-file';
export function isLibrary(project: workspaces.ProjectDefinition): boolean { export function isLibrary(project: workspaces.ProjectDefinition): boolean {
return project.extensions['projectType'] === ProjectType.Library; return project.extensions['projectType'] === ProjectType.Library;
@ -33,8 +35,11 @@ export function readWorkspaceSchema(tree: Tree) {
return workspaceSchema; return workspaceSchema;
} }
export async function resolveProject<T = any>(
export async function resolveProject(tree: Tree, name: string): Promise<Project> { tree: Tree,
name: string,
notFoundValue: T = NOT_FOUND_VALUE as unknown as any,
): Promise<Project | T> {
name = name || readWorkspaceSchema(tree).defaultProject!; name = name || readWorkspaceSchema(tree).defaultProject!;
const workspace = await getWorkspace(tree); const workspace = await getWorkspace(tree);
let definition: Project['definition'] | undefined; let definition: Project['definition'] | undefined;
@ -61,7 +66,45 @@ export async function resolveProject(tree: Tree, name: string): Promise<Project>
definition = workspace.projects.get(name); definition = workspace.projects.get(name);
} catch (_) {} } catch (_) {}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (!definition && notFoundValue !== NOT_FOUND_VALUE) {
return notFoundValue;
}
if (!definition) throw new SchematicsException(Exception.NoProject); if (!definition) throw new SchematicsException(Exception.NoProject);
return { name, definition }; return { name, definition };
} }
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) as Record<string, any> as WorkspaceSchema;
}
export function getWorkspacePath(host: Tree): string {
const possibleFiles = ['/angular.json', '/.angular.json'];
const path = possibleFiles.filter(path => host.exists(path))[0];
return path;
}
/**
* Build a default project path for generating.
* @param project The project which will have its default path generated.
* @param entryPoint The secondary-entry-point.
*/
export function buildTargetPath(
project: workspaces.ProjectDefinition,
entryPoint?: string,
): string {
if (entryPoint) {
return `${project.root}/${entryPoint}/src`;
}
return buildDefaultPath(project);
}

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

@ -20,6 +20,12 @@ class FileCopy {
const PACKAGE_TO_BUILD = 'schematics'; const PACKAGE_TO_BUILD = 'schematics';
const FILES_TO_COPY_AFTER_BUILD: (FileCopy | string)[] = [ const FILES_TO_COPY_AFTER_BUILD: (FileCopy | string)[] = [
{ src: 'src/commands/create-lib/schema.json', dest: 'commands/create-lib/schema.json' },
{ src: 'src/commands/create-lib/files-package', dest: 'commands/create-lib/files-package' },
{
src: 'src/commands/create-lib/files-secondary-entrypoint',
dest: 'commands/create-lib/files-secondary-entrypoint',
},
{ src: 'src/commands/proxy-add/schema.json', dest: 'commands/proxy-add/schema.json' }, { src: 'src/commands/proxy-add/schema.json', dest: 'commands/proxy-add/schema.json' },
{ src: 'src/commands/proxy-index/schema.json', dest: 'commands/proxy-index/schema.json' }, { src: 'src/commands/proxy-index/schema.json', dest: 'commands/proxy-index/schema.json' },
{ src: 'src/commands/proxy-refresh/schema.json', dest: 'commands/proxy-refresh/schema.json' }, { src: 'src/commands/proxy-refresh/schema.json', dest: 'commands/proxy-refresh/schema.json' },

Loading…
Cancel
Save