diff --git a/angular.json b/angular.json index 6e8a6a88c..e58a3b98f 100644 --- a/angular.json +++ b/angular.json @@ -197,7 +197,8 @@ "options": { "buildTarget": "packagr", "versionPlaceholder": "{{VERSION}}", - "ngVersionPlaceholder": "{{NG_VERSION}}" + "ngVersionPlaceholder": "{{NG_VERSION}}", + "additionalTargets": ["schematics:build"] } }, "test": { @@ -264,6 +265,20 @@ } } }, + "schematics": { + "projectType": "library", + "root": "packages/mosaic/schematics", + "sourceRoot": "packages/mosaic/schematics", + "architect": { + "build": { + "builder": "@ptsecurity/builders:typescript", + "options": { + "tsConfig": "packages/mosaic/schematics/tsconfig.lib.json", + "outDir": "dist/mosaic/schematics" + } + } + } + }, "dev-alert": { "projectType": "application", "root": "packages/mosaic-dev/alert", diff --git a/package.json b/package.json index 7cd217f80..d82bdfd8c 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "requiredAngularVersion": ">=9.0.0", "dependencies": { "@angular/animations": "^9.1.0", - "@angular/cdk": "^9.1.0", + "@angular/cdk": "^9.2.1", "@angular/common": "^9.1.0", "@angular/compiler": "^9.1.0", "@angular/core": "^9.1.0", @@ -31,9 +31,9 @@ }, "devDependencies": { "@angular-devkit/build-angular": "~0.901.0", - "@angular-devkit/build-ng-packagr": "^0.901.0", + "@angular-devkit/build-ng-packagr": "^0.901.1", "@angular-devkit/core": "^9.1.0", - "@angular-devkit/schematics": "^9.1.0", + "@angular-devkit/schematics": "^9.1.1", "@angular/cli": "^9.1.0", "@angular/compiler-cli": "^9.1.0", "@angular/platform-browser-dynamic": "^9.1.0", @@ -44,7 +44,7 @@ "@octokit/rest": "^16.2.0", "@ptsecurity/commitlint-config": "^1.0.0", "@ptsecurity/tslint-config": "^0.13.1", - "@schematics/angular": "^8.2.1", + "@schematics/angular": "^9.1.1", "@types/chalk": "^2.2.0", "@types/fs-extra": "^5.0.4", "@types/glob": "^5.0.36", @@ -64,6 +64,7 @@ "fs-extra": "^5.0.0", "glob": "^7.1.3", "gulp": "^4.0.0", + "gulp-clean": "^0.4.0", "gulp-clean-css": "^3.10.0", "gulp-cli": "^2.0.1", "gulp-flatten": "^0.3.1", @@ -106,7 +107,7 @@ "ts-node": "^7.0.1", "tsconfig-paths": "^3.9.0", "tsickle": "0.38.1", - "tslint": "^5.18.0", + "tslint": "^5.20.1", "typescript": "~3.8.3", "typescript-tslint-plugin": "^0.5.5", "wallaby-webpack": "^3.9.13", diff --git a/packages/mosaic/karma.conf.js b/packages/mosaic/karma.conf.js index f200d583c..1cbe528b0 100644 --- a/packages/mosaic/karma.conf.js +++ b/packages/mosaic/karma.conf.js @@ -11,6 +11,6 @@ module.exports = function(config) { config.set({ coverageIstanbulReporter: { dir: `${config.coverageIstanbulReporter.dir}/mosaic` - }, + } }); }; diff --git a/packages/mosaic/package.json b/packages/mosaic/package.json index 0e267fe13..a5a3c68d5 100644 --- a/packages/mosaic/package.json +++ b/packages/mosaic/package.json @@ -27,5 +27,11 @@ "@ptsecurity/mosaic-icons": "^3.0.0", "tslib": "^1.7.1" }, - "schematics": "./schematics/collection.json" + "schematics": "./schematics/collection.json", + "ng-update": { + "migrations": "./schematics/migration.json", + "packageGroup": [ + "@ptsecurity/mosaic" + ] + } } diff --git a/packages/mosaic/schematics/README.md b/packages/mosaic/schematics/README.md new file mode 100644 index 000000000..60173ef98 --- /dev/null +++ b/packages/mosaic/schematics/README.md @@ -0,0 +1,34 @@ +# Mosaic schematics + +## ng update + +The angular cli provides an interface to run automatic update scripts with +`ng update` for library authors. To achieve this we are using the +`collection.json` file to specify the schematics that need to run for each +version individually. + +### Testing + +There are tests for the update schematics that create a virtual demo app that is +used to check whether the schematic changes the correct things. + +If you want to test the schematic on a real world app you should perform the +following steps: + +- Run `ng build` - this builds the library including the schematics and puts it + into the `dist/mosaic` folder +- Link the npm dependency of the `@ptsecurity/mosaic` to the + `dist/mosaic` folder +- run + `ng update @ptsecurity/mosaic --migrateOnly=true --from="8.0.0" --to="9.0.0"` + with the correct versions respectively + +## ng add + +The ng-add schematics is used to add the **mosaic components** to a new angular +project with all its dependencies + +### Testing + +To run the unit tests with jest you can leverage the angular CLI with: +`ng test schematics` or in the watch mode `ng test schematics --watch` diff --git a/packages/mosaic/schematics/collection.json b/packages/mosaic/schematics/collection.json index 211f5059b..bb20e6085 100644 --- a/packages/mosaic/schematics/collection.json +++ b/packages/mosaic/schematics/collection.json @@ -1,22 +1,5 @@ -// This is the root config file where the schematics are defined. { - "$schema": "./node_modules/@angular-devkit/schematics/collection-schema.json", + "$schema": "../../../node_modules/@angular-devkit/schematics/collection-schema.json", "schematics": { - // Adds Mosaic to an application without changing any templates - "ng-add": { - "description": "Adds Mosaic to the application without affecting any templates", - "factory": "./ng-add/index", - "schema": "./ng-add/schema.json", - "aliases": [ - "mosaic-shell", - "install" - ] - }, - "ng-add-setup-project": { - "description": "Sets up the specified project after the ng-add dependencies have been installed.", - "private": true, - "factory": "./ng-add/setup-project", - "schema": "./ng-add/schema.json" - } } } diff --git a/packages/mosaic/schematics/migration.json b/packages/mosaic/schematics/migration.json new file mode 100644 index 000000000..47a710018 --- /dev/null +++ b/packages/mosaic/schematics/migration.json @@ -0,0 +1,10 @@ +{ + "$schema": "../../../node_modules/@angular-devkit/schematics/collection-schema.json", + "schematics": { + "update-9.0.0": { + "version": "9.0.0", + "description": "Updates Mosaic to 9.0.0", + "factory": "./ng-update/index#updateToV9" + } + } +} diff --git a/packages/mosaic/schematics/ng-add/fonts/roboto-fonts.ts b/packages/mosaic/schematics/ng-add/fonts/roboto-fonts.ts deleted file mode 100644 index 89ba88c42..000000000 --- a/packages/mosaic/schematics/ng-add/fonts/roboto-fonts.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Tree } from '@angular-devkit/schematics'; -import { getProjectFromWorkspace } from '@angular/cdk/schematics'; -import { getWorkspace } from '@schematics/angular/utility/config'; - -import { Schema } from '../schema'; - - -// TODO: add Roboto Fonts -export function addRobotoFonts(options: Schema): (host: Tree) => Tree { - - return (host: Tree) => { - const workspace = getWorkspace(host); - const project = getProjectFromWorkspace(workspace, options.project); - - return host; - }; -} diff --git a/packages/mosaic/schematics/ng-add/index.ts b/packages/mosaic/schematics/ng-add/index.ts deleted file mode 100644 index a0aaa2f9f..000000000 --- a/packages/mosaic/schematics/ng-add/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics'; -import { NodePackageInstallTask, RunSchematicTask } from '@angular-devkit/schematics/tasks'; - -import { addPackageToPackageJson, getPackageVersionFromPackageJson } from './package-config'; -import { Schema } from './schema'; -import { mosaicVersion, requiredAngularVersionRange } from './version-names'; - - -// tslint:disable-next-line:no-default-export -export default function(options: Schema): Rule { - - return (host: Tree, context: SchematicContext) => { - - const ngCoreVersionTag = getPackageVersionFromPackageJson(host, '@angular/core'); - const angularDependencyVersion = ngCoreVersionTag || requiredAngularVersionRange; - - addPackageToPackageJson(host, '@angular/cdk', angularDependencyVersion); - addPackageToPackageJson(host, '@angular/forms', angularDependencyVersion); - addPackageToPackageJson(host, '@angular/animations', angularDependencyVersion); - addPackageToPackageJson(host, '@ptsecurity/cdk', `~${mosaicVersion}`); - addPackageToPackageJson(host, '@ptsecurity/mosaic', `~${mosaicVersion}`); - - const installTaskId = context.addTask(new NodePackageInstallTask()); - - context.addTask(new RunSchematicTask('ng-add-setup-project', options), [installTaskId]); - }; -} diff --git a/packages/mosaic/schematics/ng-add/package-config.ts b/packages/mosaic/schematics/ng-add/package-config.ts deleted file mode 100644 index 11f4db1fd..000000000 --- a/packages/mosaic/schematics/ng-add/package-config.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Tree } from '@angular-devkit/schematics'; - - -function sortObjectByKeys(obj: object) { - const initialValue: object = {}; - - return Object.keys(obj).sort().reduce((result, key) => (result[key] = obj[key]) && result, initialValue); -} - -export function getPackageVersionFromPackageJson(tree: Tree, name: string): string | null { - if (!tree.exists('package.json')) { - return null; - } - - const packageJson = JSON.parse(tree.read('package.json')!.toString('utf8')); - - if (packageJson.dependencies && packageJson.dependencies[name]) { - return packageJson.dependencies[name]; - } - - return null; -} - -export function addPackageToPackageJson(host: Tree, pkg: string, version: string): Tree { - const space: number = 4; - - if (host.exists('package.json')) { - - const sourceText = host.read('package.json')!.toString('utf-8'); - const json = JSON.parse(sourceText); - - if (!json.dependencies) { - json.dependencies = {}; - } - - if (!json.dependencies[pkg]) { - json.dependencies[pkg] = version; - json.dependencies = sortObjectByKeys(json.dependencies); - } - - host.overwrite('package.json', JSON.stringify(json, null, space)); - } - - return host; -} diff --git a/packages/mosaic/schematics/ng-add/schema.json b/packages/mosaic/schematics/ng-add/schema.json deleted file mode 100644 index 3b446f9e4..000000000 --- a/packages/mosaic/schematics/ng-add/schema.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "$schema": "http://json-schema.org/schema", - "id": "mosaic-ng-add", - "title": "Mosaic ng-add schematic", - "type": "object", - "properties": { - "project": { - "type": "string", - "description": "Name of the project.", - "$default": { - "$source": "projectName" - } - }, - "theme": { - "description": "The theme to apply", - "type": "string", - "default": "default-theme", - "x-prompt": { - "message": "Choose a prebuilt theme name, or \"custom\" for a custom theme:", - "type": "list", - "items": [ - { - "value": "default-theme", - "label": "Blue/Gray" - }, - { - "value": "dark-theme", - "label": "Dark Theme" - }, - { - "value": "custom", - "label": "Custom" - } - ] - } - }, - "animations": { - "type": "boolean", - "default": true, - "description": "Whether Angular browser animations should be set up.", - "x-prompt": "Set up browser animations for Mosaic?" - } - }, - "required": [] -} diff --git a/packages/mosaic/schematics/ng-add/schema.ts b/packages/mosaic/schematics/ng-add/schema.ts deleted file mode 100644 index 9990268fc..000000000 --- a/packages/mosaic/schematics/ng-add/schema.ts +++ /dev/null @@ -1,14 +0,0 @@ -// tslint:disable-next-line naming-convention -export interface Schema { - /** Name of the project. */ - project: string; - - /** Whether gesture support should be set up. */ - gestures: boolean; - - /** Whether Angular browser animations should be set up. */ - animations: boolean; - - /** Name of pre-built theme to install. */ - theme: 'default-theme' | 'dark-theme' | 'custom'; -} diff --git a/packages/mosaic/schematics/ng-add/setup-project.ts b/packages/mosaic/schematics/ng-add/setup-project.ts deleted file mode 100644 index 9d2f02579..000000000 --- a/packages/mosaic/schematics/ng-add/setup-project.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { chain, noop, Rule, Tree } from '@angular-devkit/schematics'; -import { - addModuleImportToRootModule, - getProjectFromWorkspace, - getProjectMainFile, getProjectStyleFile, - hasNgModuleImport -} from '@angular/cdk/schematics'; -import { getWorkspace } from '@schematics/angular/utility/config'; -import { getAppModulePath } from '@schematics/angular/utility/ng-ast-utils'; -import { red, bold, italic } from 'chalk'; - -import { addRobotoFonts } from './fonts/roboto-fonts'; -import { Schema } from './schema'; -import { addThemeToAppStyles } from './theming/theming'; - - -/** - * Scaffolds the basics application, this includes: - * - Add Packages to package.json - * - Adds pre-built themes to styles.ext - * - Adds Browser Animation to app.module - */ -// tslint:disable-next-line:no-default-export -export default function(options: Schema): Rule { - return chain([ - noop(), - addAnimationsModule(options), - addThemeToAppStyles(options), - addRobotoFonts(options), - addMosaicAppStyles(options) - ]); -} - -/** Name of the Angular module that enables Angular browser animations. */ -const browserAnimationsModuleName = 'BrowserAnimationsModule'; - -/** Name of the module that switches Angular animations to a noop implementation. */ -const noopAnimationsModuleName = 'NoopAnimationsModule'; - -function addAnimationsModule(options: Schema) { - return (host: Tree) => { - - const workspace = getWorkspace(host); - const project = getProjectFromWorkspace(workspace, options.project); - - const appModulePath = getAppModulePath(host, getProjectMainFile(project)); - - if (options.animations) { - if (hasNgModuleImport(host, appModulePath, noopAnimationsModuleName)) { - console.warn(red(`Could not set up "${bold(browserAnimationsModuleName)}" ` + - `because "${bold(noopAnimationsModuleName)}" is already imported. Please manually ` + - `set up browser animations.`)); - - return; - } - - addModuleImportToRootModule(host, browserAnimationsModuleName, - '@angular/platform-browser/animations', project); - } else if (!hasNgModuleImport(host, appModulePath, browserAnimationsModuleName)) { - // Do not add the NoopAnimationsModule module if the project already explicitly uses - // the BrowserAnimationsModule. - addModuleImportToRootModule(host, noopAnimationsModuleName, - '@angular/platform-browser/animations', project); - } - - return host; - }; -} - -function addMosaicAppStyles(options: Schema) { - return (host: Tree) => { - const workspace = getWorkspace(host); - const project = getProjectFromWorkspace(workspace, options.project); - const styleFilePath = getProjectStyleFile(project); - - if (!styleFilePath) { - console.warn(red(`Could not find the default style file for this project.`)); - console.warn(red(`Please consider manually setting up the Roboto font in your CSS.`)); - - return; - } - - const buffer = host.read(styleFilePath); - - if (!buffer) { - console.warn(red(`Could not read the default style file within the project ` + - `(${italic(styleFilePath)})`)); - console.warn(red(`Please consider manually setting up the Robot font.`)); - - return; - } - - const htmlContent = buffer.toString(); - // tslint:disable-next-line:prefer-template - const insertion = '\n' + - `html, body { height: 100%; }\n` + - `body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; }\n`; - - if (htmlContent.includes(insertion)) { - return; - } - - const recorder = host.beginUpdate(styleFilePath); - - recorder.insertLeft(htmlContent.length, insertion); - host.commitUpdate(recorder); - }; -} diff --git a/packages/mosaic/schematics/ng-add/theming/create-custom-theme.ts b/packages/mosaic/schematics/ng-add/theming/create-custom-theme.ts deleted file mode 100644 index 69aa59626..000000000 --- a/packages/mosaic/schematics/ng-add/theming/create-custom-theme.ts +++ /dev/null @@ -1,8 +0,0 @@ - - -export function createCustomTheme(name: string = 'app') { - return ` - //TODO: - add description for Custom Theming - `; -} diff --git a/packages/mosaic/schematics/ng-add/theming/theming.ts b/packages/mosaic/schematics/ng-add/theming/theming.ts deleted file mode 100644 index 5763f9660..000000000 --- a/packages/mosaic/schematics/ng-add/theming/theming.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { normalize } from '@angular-devkit/core'; -import { WorkspaceProject, WorkspaceSchema } from '@angular-devkit/core/src/experimental/workspace'; -import { SchematicsException, Tree } from '@angular-devkit/schematics'; -import { getProjectFromWorkspace, getProjectStyleFile, getProjectTargetOptions } from '@angular/cdk/schematics'; -import { InsertChange } from '@schematics/angular/utility/change'; -import { getWorkspace } from '@schematics/angular/utility/config'; -import { yellow, bold, red } from 'chalk'; -import { join } from 'path'; - -import { Schema } from '../schema'; - -import { createCustomTheme } from './create-custom-theme'; - - -/** Path segment that can be found in paths that refer to a prebuilt theme. */ -const prebuiltThemePathSegment = '@ptsecurity/mosaic/prebuilt-themes'; - -/** Default file name of the custom theme that can be generated. */ -const defaultCustomThemeFilename = 'custom-theme.scss'; - -/** Object that maps a CLI target to its default builder name. */ -const defaultTargetBuilders = { - build: '@angular-devkit/build-angular:browser', - test: '@angular-devkit/build-angular:karma' -}; - - -export function addThemeToAppStyles(options: Schema): (host: Tree) => Tree { - // tslint:disable-next-line:only-arrow-functions no-function-expression - return function(host: Tree): Tree { - const workspace = getWorkspace(host); - const project = getProjectFromWorkspace(workspace, options.project); - const themeName = options.theme || 'indigo-pink'; - - if (themeName === 'custom') { - insertCustomTheme(project, options.project, host, workspace); - } else { - insertPrebuiltTheme(project, host, themeName, workspace); - } - - return host; - }; -} - -/** - * Insert a custom theme to project style file. If no valid style file could be found, a new - * Scss file for the custom theme will be created. - */ -function insertCustomTheme(project: WorkspaceProject, projectName: string, host: Tree, - workspace: WorkspaceSchema) { - - const stylesPath = getProjectStyleFile(project, 'scss'); - const themeContent = createCustomTheme(projectName); - - if (!stylesPath) { - if (!project.sourceRoot) { - throw new SchematicsException(`Could not find source root for project: "${projectName}". ` + - `Please make sure that the "sourceRoot" property is set in the workspace config.`); - } - - // Normalize the path through the devkit utilities because we want to avoid having - // unnecessary path segments and windows backslash delimiters. - const customThemePath = normalize(join(project.sourceRoot, defaultCustomThemeFilename)); - - if (host.exists(customThemePath)) { - console.warn(yellow(`Cannot create a custom Mosaic theme because - ${bold(customThemePath)} already exists. Skipping custom theme generation.`)); - - return; - } - - host.create(customThemePath, themeContent); - addThemeStyleToTarget(project, 'build', host, customThemePath, workspace); - - return; - } - - const insertion = new InsertChange(stylesPath, 0, themeContent); - const recorder = host.beginUpdate(stylesPath); - - recorder.insertLeft(insertion.pos, insertion.toAdd); - host.commitUpdate(recorder); -} - -/** - * Validates that the specified project target is configured with the default builders which are - * provided by the Angular CLI. If the configured builder does not match the default builder, - * this function can either throw or just show a warning. - */ -function validateDefaultTargetBuilder(project: WorkspaceProject, targetName: 'build' | 'test') { - const defaultBuilder = defaultTargetBuilders[targetName]; - const targetConfig = project.architect && project.architect[targetName] || - project.targets && project.targets[targetName]; - const isDefaultBuilder = targetConfig && targetConfig.builder === defaultBuilder; - - // Because the build setup for the Angular CLI can be customized by developers, we can't know - // where to put the theme file in the workspace configuration if custom builders are being - // used. In case the builder has been changed for the "build" target, we throw an error and - // exit because setting up a theme is a primary goal of `ng-add`. Otherwise if just the "test" - // builder has been changed, we warn because a theme is not mandatory for running tests - // with Mosaic. - if (!isDefaultBuilder && targetName === 'build') { - throw new SchematicsException(`Your project is not using the default builders for ` + - `"${targetName}". The Mosaic schematics cannot add a theme to the workspace ` + - `configuration if the builder has been changed.`); - } else if (!isDefaultBuilder) { - console.warn(`Your project is not using the default builders for "${targetName}". This ` + - `means that we cannot add the configured theme to the "${targetName}" target.`); - } - - return isDefaultBuilder; -} - -/** Adds a theming style entry to the given project target options. */ -function addThemeStyleToTarget(project: WorkspaceProject, targetName: 'test' | 'build', host: Tree, - assetPath: string, workspace: WorkspaceSchema) { - // Do not update the builder options in case the target does not use the default CLI builder. - if (!validateDefaultTargetBuilder(project, targetName)) { - return; - } - - const targetOptions = getProjectTargetOptions(project, targetName); - - if (!targetOptions.styles) { - targetOptions.styles = [assetPath]; - } else { - const existingStyles = targetOptions.styles.map((s) => typeof s === 'string' ? s : s.input); - - for (const [index, stylePath] of existingStyles.entries()) { - // If the given asset is already specified in the styles, we don't need to do anything. - if (stylePath === assetPath) { - return; - } - - // In case a prebuilt theme is already set up, we can safely replace the theme with the new - // theme file. If a custom theme is set up, we are not able to safely replace the custom - // theme because these files can contain custom styles, while prebuilt themes are - // always packaged and considered replaceable. - if (stylePath.includes(defaultCustomThemeFilename)) { - console.warn(red(`Could not add the selected theme to the CLI project configuration ` + - `because there is already a custom theme file referenced.`)); - console.warn(red(`Please manually add the following style file to your configuration:`)); - console.warn(yellow(` ${bold(assetPath)}`)); - - return; - } else if (stylePath.includes(prebuiltThemePathSegment)) { - targetOptions.styles.splice(index, 1); - } - } - - targetOptions.styles.unshift(assetPath); - } - - // tslint:disable-next-line:no-magic-numbers - host.overwrite('angular.json', JSON.stringify(workspace, null, 4)); -} - -/** Insert a pre-built theme into the angular.json file. */ -function insertPrebuiltTheme(project: WorkspaceProject, host: Tree, theme: string, - workspace: WorkspaceSchema) { - - // Path needs to be always relative to the `package.json` or workspace root. - const themePath = `./node_modules/@ptsecurity/mosaic/prebuilt-themes/${theme}.css`; - - addThemeStyleToTarget(project, 'build', host, themePath, workspace); - addThemeStyleToTarget(project, 'test', host, themePath, workspace); -} diff --git a/packages/mosaic/schematics/ng-add/version-names.ts b/packages/mosaic/schematics/ng-add/version-names.ts deleted file mode 100644 index d1418c622..000000000 --- a/packages/mosaic/schematics/ng-add/version-names.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** Name of the Mosaic version that is shipped together with the schematics. */ -export const mosaicVersion = - loadPackageVersionGracefully('@ptsecurity/cdk') || - loadPackageVersionGracefully('@ptsecurity/mosaic'); - -/** - * Range of Angular versions that can be used together with the Mosaic version - * that provides these schematics. - */ -export const requiredAngularVersionRange = '0.0.0-NG'; - -export const angularCDKVersion = '^8.0.0'; - - -/** Loads the full version from the given Angular package gracefully. */ -function loadPackageVersionGracefully(packageName: string): string | null { - try { - // tslint:disable-next-line:non-literal-require - return require(`${packageName}/package.json`).version; - } catch { - return null; - } -} diff --git a/packages/mosaic/schematics/ng-update/data/attribute-selectors.ts b/packages/mosaic/schematics/ng-update/data/attribute-selectors.ts new file mode 100644 index 000000000..2ff4447e5 --- /dev/null +++ b/packages/mosaic/schematics/ng-update/data/attribute-selectors.ts @@ -0,0 +1,4 @@ +import { AttributeSelectorUpgradeData, VersionChanges } from '@angular/cdk/schematics'; + + +export const attributeSelectors: VersionChanges = {}; diff --git a/packages/mosaic/schematics/ng-update/data/class-names.ts b/packages/mosaic/schematics/ng-update/data/class-names.ts new file mode 100644 index 000000000..807b30628 --- /dev/null +++ b/packages/mosaic/schematics/ng-update/data/class-names.ts @@ -0,0 +1,6 @@ +import { ClassNameUpgradeData, VersionChanges } from '@angular/cdk/schematics'; + + +export const classNames: VersionChanges = { + +}; diff --git a/packages/mosaic/schematics/ng-update/data/constructor-checks.ts b/packages/mosaic/schematics/ng-update/data/constructor-checks.ts new file mode 100644 index 000000000..14130c474 --- /dev/null +++ b/packages/mosaic/schematics/ng-update/data/constructor-checks.ts @@ -0,0 +1,6 @@ +import { ConstructorChecksUpgradeData, VersionChanges } from '@angular/cdk/schematics'; + + +export const constructorChecks: VersionChanges = { + +}; diff --git a/packages/mosaic/schematics/ng-update/data/css-selectors.ts b/packages/mosaic/schematics/ng-update/data/css-selectors.ts new file mode 100644 index 000000000..704c2ce16 --- /dev/null +++ b/packages/mosaic/schematics/ng-update/data/css-selectors.ts @@ -0,0 +1,7 @@ + +import { CssSelectorUpgradeData, VersionChanges } from '@angular/cdk/schematics'; + + +export const cssSelectors: VersionChanges = { + +}; diff --git a/packages/mosaic/schematics/ng-update/data/element-selectors.ts b/packages/mosaic/schematics/ng-update/data/element-selectors.ts new file mode 100644 index 000000000..049695a22 --- /dev/null +++ b/packages/mosaic/schematics/ng-update/data/element-selectors.ts @@ -0,0 +1,6 @@ +import { ElementSelectorUpgradeData, VersionChanges } from '@angular/cdk/schematics'; + + +export const elementSelectors: VersionChanges = { + +}; diff --git a/packages/mosaic/schematics/ng-update/data/index.ts b/packages/mosaic/schematics/ng-update/data/index.ts new file mode 100644 index 000000000..e3d8903d7 --- /dev/null +++ b/packages/mosaic/schematics/ng-update/data/index.ts @@ -0,0 +1,9 @@ +export * from './attribute-selectors'; +export * from './class-names'; +export * from './constructor-checks'; +export * from './css-selectors'; +export * from './element-selectors'; +export * from './input-names'; +export * from './method-call-checks'; +export * from './output-names'; +export * from './property-names'; diff --git a/packages/mosaic/schematics/ng-update/data/input-names.ts b/packages/mosaic/schematics/ng-update/data/input-names.ts new file mode 100644 index 000000000..8791f893c --- /dev/null +++ b/packages/mosaic/schematics/ng-update/data/input-names.ts @@ -0,0 +1,6 @@ +import { InputNameUpgradeData, VersionChanges } from '@angular/cdk/schematics'; + + +export const inputNames: VersionChanges = { + +}; diff --git a/packages/mosaic/schematics/ng-update/data/method-call-checks.ts b/packages/mosaic/schematics/ng-update/data/method-call-checks.ts new file mode 100644 index 000000000..f6ffe15ef --- /dev/null +++ b/packages/mosaic/schematics/ng-update/data/method-call-checks.ts @@ -0,0 +1,4 @@ +import { MethodCallUpgradeData, VersionChanges } from '@angular/cdk/schematics'; + + +export const methodCallChecks: VersionChanges = {}; diff --git a/packages/mosaic/schematics/ng-update/data/output-names.ts b/packages/mosaic/schematics/ng-update/data/output-names.ts new file mode 100644 index 000000000..80d21ce29 --- /dev/null +++ b/packages/mosaic/schematics/ng-update/data/output-names.ts @@ -0,0 +1,6 @@ +import { OutputNameUpgradeData, VersionChanges } from '@angular/cdk/schematics'; + + +export const outputNames: VersionChanges = { + +}; diff --git a/packages/mosaic/schematics/ng-update/data/property-names.ts b/packages/mosaic/schematics/ng-update/data/property-names.ts new file mode 100644 index 000000000..525ee359b --- /dev/null +++ b/packages/mosaic/schematics/ng-update/data/property-names.ts @@ -0,0 +1,6 @@ +import { PropertyNameUpgradeData, VersionChanges } from '@angular/cdk/schematics'; + + +export const propertyNames: VersionChanges = { + +}; diff --git a/packages/mosaic/schematics/ng-update/index.ts b/packages/mosaic/schematics/ng-update/index.ts new file mode 100644 index 000000000..ca020860a --- /dev/null +++ b/packages/mosaic/schematics/ng-update/index.ts @@ -0,0 +1,28 @@ +import { Rule, SchematicContext } from '@angular-devkit/schematics'; +import { createUpgradeRule, TargetVersion } from '@angular/cdk/schematics'; +import { green, yellow } from 'chalk'; + +import { SecondaryEntryPointsRule } from './update-9.0.0/secondary-entry-points-rule'; +import { mosaicUpgradeData } from './upgrade-data'; + + +const mosaicMigrationRules = [ + SecondaryEntryPointsRule +]; + +export function updateToV9(): Rule { + return createUpgradeRule( + TargetVersion.V9, mosaicMigrationRules, mosaicUpgradeData, onMigrationComplete); +} + + +function onMigrationComplete(context: SchematicContext, targetVersion: TargetVersion, hasFailures: boolean): void { + + context.logger.info(green(`Updated Mosaic to ${targetVersion}`)); + + if (hasFailures) { + context.logger.warn(yellow( + ' Some issues were detected but could not be fixed automatically. Please check the ' + + 'output above and fix these issues manually.')); + } +} diff --git a/packages/mosaic/schematics/ng-update/test-cases/update-9.0.0/secondary-entry-points-rule.spec.ts b/packages/mosaic/schematics/ng-update/test-cases/update-9.0.0/secondary-entry-points-rule.spec.ts new file mode 100644 index 000000000..0de357515 --- /dev/null +++ b/packages/mosaic/schematics/ng-update/test-cases/update-9.0.0/secondary-entry-points-rule.spec.ts @@ -0,0 +1,38 @@ +import { readFileSync } from 'fs'; + +import { createTestCaseSetup } from '../../../testing'; + + +/** Path to the schematic collection that includes the migrations. */ +// tslint:disable-next-line:mocha-no-side-effect-code +const migrationCollection = require.resolve('../../../migration.json'); + +describe('v9 Mosaic imports', () => { + + it('should re-map top-level Mosaic imports to the proper entry points when top-level ' + + '@ptsecurity/mosaic package does not exist', async () => { + + const { + runFixers, + appTree, + removeTempDir + } = await createTestCaseSetup( + 'update-9.0.0', + migrationCollection, + [require.resolve('./secondary-entry-points-rule_input.fixture')] + ); + + if (runFixers) { + await runFixers(); + } + + expect(appTree.readContent('projects/lib-testing/src/tests/secondary-entry-points-rule_input.ts')) + .toBe(readFileContent(require.resolve('./secondary-entry-points-rule_expected_output.fixture'))); + + removeTempDir(); + }); +}); + +export function readFileContent(filePath: string): string { + return readFileSync(filePath, 'utf8'); +} diff --git a/packages/mosaic/schematics/ng-update/test-cases/update-9.0.0/secondary-entry-points-rule_expected_output.fixture b/packages/mosaic/schematics/ng-update/test-cases/update-9.0.0/secondary-entry-points-rule_expected_output.fixture new file mode 100644 index 000000000..971c22ad4 --- /dev/null +++ b/packages/mosaic/schematics/ng-update/test-cases/update-9.0.0/secondary-entry-points-rule_expected_output.fixture @@ -0,0 +1,3 @@ +import { McButtonModule } from '@ptsecurity/mosaic/button'; + +import { McRadioModule } from '@ptsecurity/mosaic/radio'; diff --git a/packages/mosaic/schematics/ng-update/test-cases/update-9.0.0/secondary-entry-points-rule_input.fixture b/packages/mosaic/schematics/ng-update/test-cases/update-9.0.0/secondary-entry-points-rule_input.fixture new file mode 100644 index 000000000..3c5dd9501 --- /dev/null +++ b/packages/mosaic/schematics/ng-update/test-cases/update-9.0.0/secondary-entry-points-rule_input.fixture @@ -0,0 +1,3 @@ +import { McButtonModule } from '@ptsecurity/mosaic'; + +import { McRadioModule } from '@ptsecurity/mosaic'; diff --git a/packages/mosaic/schematics/ng-update/update-9.0.0/mosaic-symbols.json b/packages/mosaic/schematics/ng-update/update-9.0.0/mosaic-symbols.json new file mode 100644 index 000000000..c0f3b9b78 --- /dev/null +++ b/packages/mosaic/schematics/ng-update/update-9.0.0/mosaic-symbols.json @@ -0,0 +1,290 @@ +{ + "AUTOCOMPLETE_OPTION_HEIGHT": "autocomplete", + "AUTOCOMPLETE_PANEL_HEIGHT": "autocomplete", + "McAutocomplete": "autocomplete", + "McAutocompleteTrigger": "autocomplete", + "McAutocompleteOrigin": "autocomplete", + "McAutocompleteSelectedEvent": "autocomplete", + "MC_AUTOCOMPLETE_DEFAULT_OPTIONS": "autocomplete", + "MC_AUTOCOMPLETE_DEFAULT_OPTIONS_FACTORY": "autocomplete", + "MC_AUTOCOMPLETE_SCROLL_STRATEGY": "autocomplete", + "MC_AUTOCOMPLETE_SCROLL_STRATEGY_FACTORY": "autocomplete", + "MC_AUTOCOMPLETE_SCROLL_STRATEGY_FACTORY_PROVIDER": "autocomplete", + "McAnchor": "button", + "McButton": "button", + "McButtonModule": "button", + "McButtonToggle": "button-toggle", + "McButtonToggleChange": "button-toggle", + "McButtonToggleGroup": "button-toggle", + "McButtonToggleModule": "button-toggle", + "ToggleType": "button-toggle", + "McCard": "card", + "McCardModule": "card", + "MC_CHECKBOX_CLICK_ACTION": "checkbox", + "MC_CHECKBOX_CONTROL_VALUE_ACCESSOR": "checkbox", + "MC_CHECKBOX_REQUIRED_VALIDATOR": "checkbox", + "McCheckbox": "checkbox", + "McCheckboxChange": "checkbox", + "McCheckboxClickAction": "checkbox", + "McCheckboxModule": "checkbox", + "McCheckboxRequiredValidator": "checkbox", + "TransitionCheckState": "checkbox", + "AnimationCurves": "core", + "McCommonModule": "core", + "CanColor": "core", + "CanColorCtor": "core", + "CanDisable": "core", + "CanDisableCtor": "core", + "CanUpdateErrorState": "core", + "CanUpdateErrorStateCtor": "core", + "ErrorStateMatcher": "core", + "FloatLabelType": "core", + "HasTabIndex": "core", + "HasTabIndexCtor": "core", + "LabelOptions": "core", + "MC_LABEL_GLOBAL_OPTIONS": "core", + "MC_OPTION_PARENT_COMPONENT": "core", + "MC_SANITY_CHECKS": "core", + "McLine": "core", + "McLineModule": "core", + "McLineSetter": "core", + "McOptgroup": "core", + "McOption": "core", + "McOptionModule": "core", + "McOptionParentComponent": "core", + "McOptionSelectionChange": "core", + "McPseudoCheckbox": "core", + "McPseudoCheckboxModule": "core", + "McPseudoCheckboxState": "core", + "mixinColor": "core", + "mixinDisabled": "core", + "mixinErrorState": "core", + "mixinInitialized": "core", + "mixinTabIndex": "core", + "ShowOnDirtyErrorStateMatcher": "core", + "ThemePalette": "core", + + "MC_DATEPICKER_SCROLL_STRATEGY": "datepicker", + "MC_DATEPICKER_SCROLL_STRATEGY_FACTORY": "datepicker", + "MC_DATEPICKER_SCROLL_STRATEGY_FACTORY_PROVIDER": "datepicker", + "MC_DATEPICKER_VALIDATORS": "datepicker", + "MC_DATEPICKER_VALUE_ACCESSOR": "datepicker", + "McCalendar": "datepicker", + "McCalendarBody": "datepicker", + "McCalendarCell": "datepicker", + "McCalendarCellCssClasses": "datepicker", + "McCalendarHeader": "datepicker", + "McCalendarView": "datepicker", + "McDatepicker": "datepicker", + "McDatepickerContent": "datepicker", + "McDatepickerInput": "datepicker", + "McDatepickerInputEvent": "datepicker", + "McDatepickerIntl": "datepicker", + "McDatepickerModule": "datepicker", + "McDatepickerToggle": "datepicker", + "McDatepickerToggleIcon": "datepicker", + "McMonthView": "datepicker", + "McMultiYearView": "datepicker", + "McYearView": "datepicker", + "yearsPerPage": "datepicker", + "yearsPerRow": "datepicker", + "McDivider": "divider", + "McDividerModule": "divider", + + "McDropdown": "dropdown", + "McDropdownModule": "dropdown", + "McDropdownItem": "dropdown", + "McDropdownTrigger": "dropdown", + "McDropdownContent": "dropdown", + "MC_DROPDOWN_SCROLL_STRATEGY_FACTORY_PROVIDER": "dropdown", + + "McFormField": "form-field", + "McFormFieldControl": "form-field", + "McFormFieldModule": "form-field", + "McHint": "form-field", + "McPrefix": "form-field", + "McSuffix": "form-field", + + "McIcon": "icon", + "McIconModule": "icon", + "McIconCSSStyler": "icon", + + "McInput": "input", + "McInputModule": "input", + "McNumberInput": "input", + "McInputMono": "input", + "MinValidator": "input", + "MaxValidator": "input", + + "McLink": "link", + "McLinkModule": "link", + + "MC_SELECTION_LIST_VALUE_ACCESSOR": "list", + "McList": "list", + "McListItem": "list", + "McListSelection": "list", + "McListModule": "list", + "McListOption": "list", + + "McModalComponent": "modal", + "McModalTitle": "modal", + "McModalBody": "modal", + "McModalFooter": "modal", + "McModalModule": "modal", + "McModalControlService": "modal", + "McModalService": "modal", + "McModalRef": "modal", + "IModalOptions": "modal", + "ConfirmType": "modal", + "ModalType": "modal", + + "McNavbarModule": "navbar", + "McNavbar": "navbar", + "McNavbarContainer": "navbar", + "McNavbarTitle": "navbar", + "McNavbarItem": "navbar", + "McNavbarBrand": "navbar", + "McNavbarLogo": "navbar", + + "McPopover": "popover", + "McPopoverModule": "popover", + "MC_POPOVER_SCROLL_STRATEGY_FACTORY_PROVIDER": "popover", + + "McProgressBar": "progress-bar", + "McProgressBarModule": "progress-bar", + + "McProgressSpinner": "progress-spinner", + "McProgressSpinnerModule": "progress-spinner", + "ProgressSpinnerMode": "progress-spinner", + + "McRadioButton": "radio", + "McRadioModule": "radio", + "McRadioChange": "radio", + "McRadioGroup": "radio", + + "McSelect": "select", + "mcSelectAnimations": "select", + "McSelectChange": "select", + "McSelectModule": "select", + "McSelectTrigger": "select", + "SELECT_PANEL_INDENT_PADDING_X": "select", + "SELECT_PANEL_MAX_HEIGHT": "select", + "SELECT_PANEL_PADDING_X": "select", + "SELECT_PANEL_VIEWPORT_PADDING": "select", + "transformPanel": "select", + + "McSidebarClosed": "sidebar", + "McSidebarOpened": "sidebar", + "McSidebar": "sidebar", + "McSidebarModule": "sidebar", + "SidebarPositions": "sidebar", + + "McSidepanelContainerComponent": "sidepanel", + "McSidepanelClose": "sidepanel", + "McSidepanelHeader": "sidepanel", + "McSidepanelBody": "sidepanel", + "McSidepanelFooter": "sidepanel", + "McSidepanelActions": "sidepanel", + "McSidepanelModule": "sidepanel", + "McSidepanelService": "sidepanel", + "MC_SIDEPANEL_DEFAULT_OPTIONS": "sidepanel", + "MC_SIDEPANEL_WITH_INDENT": "sidepanel,", + "MC_SIDEPANEL_WITH_SHADOW": "sidepanel", + "mcSidepanelAnimations": "sidepanel", + "McSidepanelAnimationState": "sidepanel", + "mcSidepanelTransformAnimation": "sidepanel", + "McSidepanelRef": "sidepanel", + + "McGutterDirective": "splitter", + "McSplitterAreaDirective": "splitter", + "McSplitterComponent": "splitter", + "McSplitterModule": "splitter", + "Direction": "splitter", + + "McTable": "table", + "McTableModule": "table", + + "McTabGroupBase": "tabs", + "McTabHeaderBase": "tabs", + "McTabLinkBase": "tabs", + "McTabNavBase": "tabs", + "MC_TABS_CONFIG": "tabs", + "McTab": "tabs", + "McTabBody": "tabs", + "McTabBodyOriginState": "tabs", + "McTabBodyPortal": "tabs", + "McTabBodyPositionState": "tabs", + "McTabChangeEvent": "tabs", + "McTabContent": "tabs", + "McTabGroup": "tabs", + "McTabHeader": "tabs", + "McTabHeaderPosition": "tabs", + "McTabLabel": "tabs", + "McTabLabelWrapper": "tabs", + "McTabLink": "tabs", + "McTabNav": "tabs", + "mcTabsAnimations": "tabs", + "McTabsConfig": "tabs", + "McTabsModule": "tabs", + "ScrollDirection": "tabs", + + "McTagList": "tags", + "McTag": "tags", + "McTagsModule": "tags", + "McTagInput": "tags", + "McTagTrailingIcon": "tags", + "McTagAvatar": "tags", + "McTagRemove": "tags", + "McTagListChange": "tags", + "McTagTextControl": "tags", + "McTagInputEvent": "tags", + + "McTextarea": "textarea", + "McTextareaModule": "textarea", + "MC_TEXTAREA_VALUE_ACCESSOR": "textarea", + + "McTimepicker": "timepicker", + "McTimepickerModule": "timepicker", + + "McToggleComponent": "toggle", + "McToggleModule": "toggle", + "McToggleChange": "toggle", + + "getMcTooltipInvalidPositionError": "tooltip", + "MC_TOOLTIP_SCROLL_STRATEGY": "tooltip", + "MC_TOOLTIP_SCROLL_STRATEGY_FACTORY": "tooltip", + "MC_TOOLTIP_SCROLL_STRATEGY_FACTORY_PROVIDER": "tooltip", + "McTooltip": "tooltip", + "McTooltipModule": "tooltip", + "TooltipComponent": "tooltip", + "TooltipPosition": "tooltip", + + "McTreeSelection": "tree", + "McTreeOption": "tree", + "MC_TREE_OPTION_PARENT_COMPONENT": "tree", + "McTreeOptionEvent": "tree", + "McTreeOptionChange": "tree", + "McTreeNodeDef": "tree", + "McTreeNodePadding": "tree", + "McTreeNodeToggleComponent": "tree", + "McTreeNodeToggleDirective": "tree", + "McTreeModule": "tree", + "McTreeFlattener": "tree", + "McTreeFlatDataSource": "tree", + "McTreeNestedDataSource": "tree", + + "McTreeSelectModule": "tree-select", + "McTreeSelect": "tree-select", + "McTreeSelectTrigger": "tree-select", + "McTreeSelectChange": "tree-select", + "CommonModule": "tree-select", + + "McVerticalNavbarModule": "vertical-navbar", + "McVerticalNavbar": "vertical-navbar", + "McVerticalNavbarTitle": "vertical-navbar", + "McVerticalNavbarItem": "vertical-navbar", + "McVerticalNavbarItemIcon": "vertical-navbar", + "McVerticalNavbarItemBadge": "vertical-navbar", + "McVerticalNavbarHeader": "vertical-navbar", + "toggleVerticalNavbarAnimation": "vertical-navbar" +} diff --git a/packages/mosaic/schematics/ng-update/update-9.0.0/secondary-entry-points-rule.ts b/packages/mosaic/schematics/ng-update/update-9.0.0/secondary-entry-points-rule.ts new file mode 100644 index 000000000..9ac4ec7cc --- /dev/null +++ b/packages/mosaic/schematics/ng-update/update-9.0.0/secondary-entry-points-rule.ts @@ -0,0 +1,192 @@ +import { MigrationRule, TargetVersion } from '@angular/cdk/schematics'; +import * as ts from 'typescript'; + + +const ONLY_SUBPACKAGE_FAILURE_STR = `Importing from "@ptsecurity/mosaic" is deprecated. ` + + `Instead import from the entry-point the symbol belongs to.`; + +const NO_IMPORT_NAMED_SYMBOLS_FAILURE_STR = `Imports from Mosaic should import ` + + `specific symbols rather than importing the entire library.`; + + +const legacyMosaicModuleSpecifier = '@ptsecurity/mosaic'; +const currentMosaicModuleSpecifier = '@ptsecurity/mosaic'; + +const MOSAIC_AC_FILEPATH_REGEX = new RegExp( + `${legacyMosaicModuleSpecifier}/(.*?)/` +); + +// tslint:disable-next-line:no-var-requires +const ENTRY_POINT_MAPPINGS: {[name: string]: string} = require('./mosaic-symbols.json'); + +export class SecondaryEntryPointsRule extends MigrationRule { + + printer = ts.createPrinter(); + + // Only enable this rule if the migration targets version 8. + ruleEnabled = this.targetVersion === TargetVersion.V8 || this.targetVersion === TargetVersion.V9; + + // tslint:disable-next-line:max-func-body-length + visitNode(declaration: ts.Node): void { + + if ( + !ts.isImportDeclaration(declaration) || + !ts.isStringLiteralLike(declaration.moduleSpecifier) + ) { + return; + } + + const importLocation = declaration.moduleSpecifier.text; + // skip check - if the import module is not @ptsecurity/mosaic + if (importLocation !== legacyMosaicModuleSpecifier) { + return; + } + + // If no import clause is found, or nothing is named as a binding in the + // import, add failure saying to import symbols in clause. + if (!declaration.importClause || !declaration.importClause.namedBindings) { + this.createFailureAtNode(declaration, NO_IMPORT_NAMED_SYMBOLS_FAILURE_STR); + + return; + } + + // All named bindings in import clauses must be named symbols, otherwise add + // failure saying to import symbols in clause. + if (!ts.isNamedImports(declaration.importClause.namedBindings)) { + this.createFailureAtNode(declaration, NO_IMPORT_NAMED_SYMBOLS_FAILURE_STR); + + return; + } + + // If no symbols are in the named bindings then add failure saying to + // import symbols in clause. + if (!declaration.importClause.namedBindings.elements.length) { + this.createFailureAtNode(declaration, NO_IMPORT_NAMED_SYMBOLS_FAILURE_STR); + + return; + } + + // Map which consists of secondary entry-points and import specifiers which are used + // within the current import declaration. + const importMap = new Map(); + + // Determine the subpackage each symbol in the namedBinding comes from. + for (const element of declaration.importClause.namedBindings.elements) { + + const elementName = element.propertyName ? element.propertyName : element.name; + + const moduleName = resolveModuleName(elementName, this.typeChecker) || + ENTRY_POINT_MAPPINGS[elementName.text] || null; + + if (!moduleName) { + + console.log(`"${element.getText()}" was not found in the Mosaic library.`); + this.createFailureAtNode( + element, `"${element.getText()}" was not found in the Mosaic library.`); + + return; + } + + // The module name where the symbol is defined e.g. card, dialog. The + // first capture group is contains the module name. + if (importMap.has(moduleName)) { + importMap.get(moduleName)!.push(element); + } else { + importMap.set(moduleName, [element]); + } + } + + const singleQuoteImport = declaration.moduleSpecifier.getText()[0] === `'`; + + // Transforms the import declaration into multiple import declarations that import + // the given symbols from the individual secondary entry-points. For example: + // import { McCardModule } from '@ptsecurity/mosaic/card'; + // import { McRadioModule } from '@ptsecurity/mosaic/radio'; + const newImportStatements = + Array.from(importMap.entries()) + .sort() + .map(([name, elements]) => { + const newImport = ts.createImportDeclaration( + undefined, undefined, + ts.createImportClause(undefined, ts.createNamedImports(elements)), + createStringLiteral(`${currentMosaicModuleSpecifier}/${name}`, singleQuoteImport)); + + return this.printer.printNode( + ts.EmitHint.Unspecified, newImport, declaration.getSourceFile()); + }) + .join('\n'); + + // Without any import statements that were generated, we can assume that this was an empty + // import declaration. We still want to add a failure in order to make developers aware that + // importing from "@ptsecurity/mosaic" is deprecated. + if (!newImportStatements) { + this.createFailureAtNode(declaration.moduleSpecifier, ONLY_SUBPACKAGE_FAILURE_STR); + + return; + } + + const recorder = this.getUpdateRecorder(declaration.moduleSpecifier.getSourceFile().fileName); + + // Perform the replacement that switches the primary entry-point import to + // the individual secondary entry-point imports. + recorder.remove(declaration.getStart(), declaration.getWidth()); + recorder.insertRight(declaration.getStart(), newImportStatements); + } +} + +/** + * Creates a string literal from the specified text. + * @param text Text of the string literal. + * @param singleQuotes Whether single quotes should be used when printing the literal node. + */ +function createStringLiteral(text: string, singleQuotes: boolean): ts.StringLiteral { + const literal = ts.createStringLiteral(text); + // See: https://github.com/microsoft/TypeScript/blob/master/src/compiler/utilities.ts#L584-L590 + // tslint:disable-next-line: no-string-literal + literal['singleQuote'] = singleQuotes; + + return literal; +} + +function getDeclarationSymbolOfNode(node: ts.Node, checker: ts.TypeChecker): ts.Symbol | undefined { + // tslint:disable-next-line:no-reserved-keywords + const symbol = checker.getSymbolAtLocation(node); + + // Symbols can be aliases of the declaration symbol. e.g. in named import specifiers. + // We need to resolve the aliased symbol back to the declaration symbol. + // tslint:disable-next-line:no-bitwise + if (symbol && (symbol.flags & ts.SymbolFlags.Alias) !== 0) { + return checker.getAliasedSymbol(symbol); + } + + return symbol; +} + +function resolveModuleName(node: ts.Identifier, typeChecker: ts.TypeChecker) { + // Get the symbol for the named binding element. Note that we cannot determine the + // value declaration based on the type of the element as types are not necessarily + // specific to a given secondary entry-point (e.g. exports with the type of "string") + // would resolve to the module types provided by TypeScript itself. + // tslint:disable-next-line:no-reserved-keywords + const symbol = getDeclarationSymbolOfNode(node, typeChecker); + + // If the symbol can't be found, or no declaration could be found within + // the symbol, add failure to report that the given symbol can't be found. + if (!symbol || + !(symbol.valueDeclaration || (symbol.declarations && symbol.declarations.length !== 0))) { + return null; + } + + // The filename for the source file of the node that contains the + // first declaration of the symbol. All symbol declarations must be + // part of a defining node, so parent can be asserted to be defined. + const resolvedNode = symbol.valueDeclaration || symbol.declarations[0]; + const sourceFile = resolvedNode.getSourceFile().fileName; + + // File the module the symbol belongs to from a regex match of the + // filename. This will always match since only "@ptsecurity/mosaic" + // elements are analyzed. + const matches = sourceFile.match(MOSAIC_AC_FILEPATH_REGEX); + + return matches ? matches[1] : null; +} diff --git a/packages/mosaic/schematics/ng-update/upgrade-data.ts b/packages/mosaic/schematics/ng-update/upgrade-data.ts new file mode 100644 index 000000000..eb81e95b9 --- /dev/null +++ b/packages/mosaic/schematics/ng-update/upgrade-data.ts @@ -0,0 +1,26 @@ +import { RuleUpgradeData } from '@angular/cdk/schematics'; + +import { + attributeSelectors, + classNames, + constructorChecks, + cssSelectors, + elementSelectors, + inputNames, + methodCallChecks, + outputNames, + propertyNames +} from './data'; + + +export const mosaicUpgradeData: RuleUpgradeData = { + attributeSelectors, + classNames, + constructorChecks, + cssSelectors, + elementSelectors, + inputNames, + methodCallChecks, + outputNames, + propertyNames +}; diff --git a/packages/mosaic/schematics/testing/index.ts b/packages/mosaic/schematics/testing/index.ts new file mode 100644 index 000000000..f76e9d64d --- /dev/null +++ b/packages/mosaic/schematics/testing/index.ts @@ -0,0 +1 @@ +export * from './test-case-setup'; diff --git a/packages/mosaic/schematics/testing/test-case-setup.ts b/packages/mosaic/schematics/testing/test-case-setup.ts new file mode 100644 index 000000000..a792dbd1a --- /dev/null +++ b/packages/mosaic/schematics/testing/test-case-setup.ts @@ -0,0 +1,166 @@ +import { getSystemPath, normalize } from '@angular-devkit/core'; +import { TempScopedNodeJsSyncHost } from '@angular-devkit/core/node/testing'; +import * as virtualFs from '@angular-devkit/core/src/virtual-fs/host'; +import { HostTree, Tree } from '@angular-devkit/schematics'; +import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'; +import { readFileSync, rmdirSync } from 'fs'; +import { basename, extname } from 'path'; + + +/** Create a base app used for testing. */ +export async function createTestApp( + runner: SchematicTestRunner, + appOptions: { name?: string } = {}, + tree?: Tree +): Promise { + + const workspaceTree = await runner + .runExternalSchematicAsync( + '@schematics/angular', + 'workspace', + { + name: 'workspace', + version: '6.0.0', + newProjectRoot: 'projects' + }, + tree + ) + .toPromise(); + + return runner + .runExternalSchematicAsync( + '@schematics/angular', + 'application', + { name: '@ptsecurity/mosaic', ...appOptions }, + workspaceTree + ) + .toPromise(); +} + + +export async function createFileSystemTestApp(runner: SchematicTestRunner): Promise { + const tempFileSystemHost = new TempScopedNodeJsSyncHost(); + const hostTree = new HostTree(tempFileSystemHost); + + const appTree: UnitTestTree = await createTestApp( + runner, + { name: 'lib-testing' }, + hostTree + ); + + const tempPath = getSystemPath(tempFileSystemHost.root); + + // Since the TypeScript compiler API expects all files to be present on the real file system, we + // map every file in the app tree to a temporary location on the file system. + appTree.files.forEach((f) => { + writeFile(f, appTree.readContent(f)); + }); + + return { + appTree, + tempFileSystemHost, + tempPath, + writeFile, + removeTempDir: () => { + rmdirSync(tempPath, { recursive: true }); + } + }; + + function writeFile(filePath: string, content: string): void { + tempFileSystemHost.sync.write( + normalize(filePath), + virtualFs.stringToFileBuffer(content) + ); + } +} + +interface ITestCaseSetup { + appTree: UnitTestTree; + tempPath: string; + tempFileSystemHost?: TempScopedNodeJsSyncHost; + runFixers?(): Promise<{ logOutput: string }>; + removeTempDir(): void; + writeFile(filePath: string, content: string): void; +} + +export async function createTestCaseSetup( + migrationName: string, + collectionPath: string, + inputFiles: string[] +): Promise { + + const runner = new SchematicTestRunner('schematics', collectionPath); + const initialWorkingDir = process.cwd(); + + let logOutput = ''; + runner.logger.subscribe((entry) => (logOutput += `${entry.message}\n`)); + + const { + appTree, + tempPath, + writeFile, + removeTempDir + } = await createFileSystemTestApp(runner); + + patchTypeScriptDefaultLib(appTree); + + // Write each test-case input to the file-system. This is necessary because otherwise + // TypeScript compiler API won't be able to pick up the test cases. + inputFiles.forEach((inputFilePath) => { + + const inputTestName = basename(inputFilePath, extname(inputFilePath)); + const relativePath = `projects/lib-testing/src/tests/${inputTestName}.ts`; + const inputContent = readFileSync(inputFilePath, 'utf8'); + + writeFile(relativePath, inputContent); + }); + + const testAppTsconfigPath = 'projects/lib-testing/tsconfig.app.json'; + const testAppTsconfig = JSON.parse(appTree.readContent(testAppTsconfigPath)); + + // include all TypeScript files in the project. Otherwise all test input + // files won't be part of the program and cannot be migrated. + testAppTsconfig.include.push('src/**/*.ts'); + + // tslint:disable-next-line:no-magic-numbers + writeFile(testAppTsconfigPath, JSON.stringify(testAppTsconfig, null, 4)); + + // tslint:disable-next-line:only-arrow-functions no-function-expression + const runFixers = async function(): Promise<{ logOutput: string }> { + // Switch to the new temporary directory to simulate that "ng update" is ran + // from within the project. + process.chdir(tempPath); + + await runner.runSchematicAsync(migrationName, {}, appTree).toPromise(); + + // Switch back to the initial working directory. + process.chdir(initialWorkingDir); + + return { logOutput }; + }; + + return { appTree, writeFile, tempPath, removeTempDir, runFixers }; +} + +/** + * Patches the specified virtual file system tree to be able to read the TypeScript + * default library typings. These need to be readable in unit tests because otherwise + * type checking within migration rules is not working as in real applications. + */ +function patchTypeScriptDefaultLib(tree: Tree): void { + // tslint:disable-next-line: no-unbound-method + const originalRead = tree.read; + + // tslint:disable-next-line: no-any + tree.read = function(filePath: string): Buffer | any { + // In case a file within the TypeScript package is requested, we read the file from + // the real file system. This is necessary because within unit tests, the "typeScript" + // package from within the Bazel "@npm" repository is used. The virtual tree can't be + // used because the "@npm" repository directory is not part of the virtual file system. + if (filePath.match(/node_modules[/\\]typescript/)) { + return readFileSync(filePath); + } else { + return originalRead.apply(this, arguments); + } + }; +} diff --git a/packages/mosaic/schematics/tsconfig.json b/packages/mosaic/schematics/tsconfig.json index 32eafede4..c834cdf35 100644 --- a/packages/mosaic/schematics/tsconfig.json +++ b/packages/mosaic/schematics/tsconfig.json @@ -1,39 +1,21 @@ { + "extends": "../../../tsconfig.json", "compilerOptions": { - "lib": [ - "es2017" - ], - "module": "commonjs", - "moduleResolution": "node", - "outDir": "../../../dist/packages/mosaic/schematics", - "noEmitOnError": false, - "strictNullChecks": true, - "skipDefaultLibCheck": true, - "noUnusedLocals": false, - "noUnusedParameters": false, - "skipLibCheck": true, - "sourceMap": true, - "declaration": true, + "rootDir": ".", + "resolveJsonModule": true, + "downlevelIteration": true, "target": "es6", "types": [ - "jasmine", - "node" - ], - "baseUrl": ".", - "paths": { - "@ptsecurity/cdk/schematics": [ - "../../cdk/schematics" - ] - } + "node", + "jasmine" + ] }, - "references": [ - { - "path": "../../cdk/schematics" - } + "files": [ + "./collection.json", + "./migration.json" ], - "exclude": [ - "**/*.spec.ts", - // Exclude the test-setup utility files. Those should not be part of the output. - "test-setup/**/*.ts" + "include": [ + "**/*.ts", + "**/*.json" ] } diff --git a/packages/mosaic/schematics/tsconfig.lib-test.json b/packages/mosaic/schematics/tsconfig.lib-test.json new file mode 100644 index 000000000..baade6142 --- /dev/null +++ b/packages/mosaic/schematics/tsconfig.lib-test.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/schematics-test", + "module": "commonjs", + "target": "es2015", + "declaration": true, + "inlineSources": true + } +} diff --git a/packages/mosaic/schematics/tsconfig.lib.json b/packages/mosaic/schematics/tsconfig.lib.json new file mode 100644 index 000000000..dbbb9b71a --- /dev/null +++ b/packages/mosaic/schematics/tsconfig.lib.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "module": "commonjs", + "target": "es2015", + "declaration": true, + "inlineSources": true + }, + "exclude": [ + "**/*.spec.ts", + "testing/**/*.ts", + "ng-update/test-cases/**/*" + ] +} diff --git a/packages/mosaic/test.ts b/packages/mosaic/test.ts index eb6844966..df446ba8c 100644 --- a/packages/mosaic/test.ts +++ b/packages/mosaic/test.ts @@ -27,7 +27,7 @@ patchTestBedToDestroyFixturesAfterEveryTest(testBed); * destruction are thrown instead of silently logged. Also runs TestBed.resetTestingModule after * each unit test. * - * Without this patch, the combination of two behaviors is problematic for Angular Material: + * Without this patch, the combination of two behaviors is problematic: * - TestBed.resetTestingModule catches errors thrown on fixture destruction and logs them without * the errors ever being thrown. This means that any component errors that occur in ngOnDestroy * can encounter errors silently and still pass unit tests. diff --git a/tools/builders/builders.json b/tools/builders/builders.json index 3fb4ec290..2a5be8559 100644 --- a/tools/builders/builders.json +++ b/tools/builders/builders.json @@ -5,6 +5,11 @@ "implementation": "./packager", "schema": "./packager/schema.json", "description": "Packaging" + }, + "typescript": { + "implementation": "./typescript", + "schema": "./typescript/schema.json", + "description": "Compiles typescript files using tsc" } } } diff --git a/tools/builders/typescript/index.ts b/tools/builders/typescript/index.ts new file mode 100644 index 000000000..6593feea0 --- /dev/null +++ b/tools/builders/typescript/index.ts @@ -0,0 +1,71 @@ +import { BuilderContext, BuilderOutput, createBuilder } from '@angular-devkit/architect'; +import { JsonObject } from '@angular-devkit/core'; +import { ExecOptions, exec } from 'child_process'; +import { statSync } from 'fs'; +import { join } from 'path'; + +import { ITypescriptBuilderOptions } from './schema'; + + +export async function executeCommand(command: string, cwd?: string): Promise { + // tslint:disable-next-line:no-magic-numbers + const maxBuffer = 1024 * 1024 * 10; + + const options: ExecOptions = { + cwd: cwd || process.cwd(), + maxBuffer + }; + + return new Promise((resolve, reject) => { + exec(command, options, (err, stdout) => { + if (err !== null) { + reject(stdout); + } else { + resolve(stdout); + } + }); + }); +} + +async function run(options: ITypescriptBuilderOptions, context: BuilderContext): Promise { + const configFile = join(context.workspaceRoot, options.tsConfig); + + let outDirArgument = ''; + + if (options.outDir) { + outDirArgument = ` --outDir ${join(context.workspaceRoot, options.outDir)}`; + } + + if (!statSync(configFile).isFile()) { + context.logger.error( + 'No tsconfig.json file found for compiling. Please provide it via the tsConfig option.' + ); + + return { + success: false + }; + } + + try { + const logOutput = await executeCommand( + `node_modules/.bin/tsc -p ${configFile}${outDirArgument}` + ); + + if (logOutput) { + context.logger.info(logOutput); + } + } catch (error) { + context.logger.error(error); + + return { + success: false + }; + } + + return { + success: true + }; +} + +// tslint:disable-next-line:no-default-export +export default createBuilder(run); diff --git a/tools/builders/typescript/schema.json b/tools/builders/typescript/schema.json new file mode 100644 index 000000000..d86db2cf2 --- /dev/null +++ b/tools/builders/typescript/schema.json @@ -0,0 +1,17 @@ +{ + "$schema": "http://json-schema.org/schema", + "id": "typescript", + "type": "object", + "properties": { + "tsConfig": { + "type": "string", + "description": "The name of the Typescript configuration file." + }, + "outDir": { + "type": "string", + "description": "The output directory for the compiled files" + } + }, + "additionalProperties": false, + "required": [] +} diff --git a/tools/builders/typescript/schema.ts b/tools/builders/typescript/schema.ts new file mode 100644 index 000000000..f6ab3c62b --- /dev/null +++ b/tools/builders/typescript/schema.ts @@ -0,0 +1,4 @@ +export interface ITypescriptBuilderOptions { + tsConfig: string; + outDir?: string; +} diff --git a/tools/gulp/gulpfile.ts b/tools/gulp/gulpfile.ts index d88e9b03e..09de2c29d 100644 --- a/tools/gulp/gulpfile.ts +++ b/tools/gulp/gulpfile.ts @@ -4,3 +4,4 @@ import './tasks/lint'; import './tasks/docs'; import './tasks/styles'; +import './tasks/schematic'; diff --git a/tools/gulp/tasks/schematic.ts b/tools/gulp/tasks/schematic.ts new file mode 100644 index 000000000..eb6025222 --- /dev/null +++ b/tools/gulp/tasks/schematic.ts @@ -0,0 +1,38 @@ +import { copySync } from 'fs-extra'; +import { series, task } from 'gulp'; +import { join } from 'path'; + +import { buildConfig } from '../build-config'; +import { cleanTask, execNodeTask } from '../utils/helpers'; + + +const schematicsDir = join(buildConfig.packagesDir, 'mosaic/schematics'); +const tsconfigLibTestFile = join(schematicsDir, 'tsconfig.lib-test.json'); +const targetPath = join(buildConfig.outputDir, 'schematics-test'); + + +task('clean:schematics-test', cleanTask([targetPath])); + +task('build:schematics-test', execNodeTask( + 'typescript', + 'tsc', + [ '-p', tsconfigLibTestFile ] +)); + +task('test:schematics-update', execNodeTask( + 'jasmine', + 'jasmine', + [ 'dist/schematics-test/ng-update/test-cases/**/*.spec.js' ] +)); + +task('schematics:copy-fixtures', (done) => { + copySync(schematicsDir, targetPath); + done(); +}); + +task('unit:schematics', series( + 'clean:schematics-test', + 'build:schematics-test', + 'schematics:copy-fixtures', + 'test:schematics-update' +)); diff --git a/tools/gulp/utils/helpers.ts b/tools/gulp/utils/helpers.ts index 33257f7e3..914dc09f3 100644 --- a/tools/gulp/utils/helpers.ts +++ b/tools/gulp/utils/helpers.ts @@ -1,9 +1,14 @@ // tslint:disable:no-var-requires import * as child_process from 'child_process'; +import { src, TaskFunction } from 'gulp'; +const gulpClean = require('gulp-clean'); const resolveBin = require('resolve-bin'); +export function cleanTask(glob: string | string[]): TaskFunction { + return () => src(glob, { read: false, allowEmpty: true }).pipe(gulpClean(null)); +} export function execTask(binPath: string, args: string[], options: IExecTaskOptions = {}) { return (done: (err?: string) => void) => { diff --git a/yarn.lock b/yarn.lock index ea01166f9..11f7059d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -77,7 +77,7 @@ webpack-subresource-integrity "1.4.0" worker-plugin "4.0.2" -"@angular-devkit/build-ng-packagr@^0.901.0": +"@angular-devkit/build-ng-packagr@^0.901.1": version "0.901.1" resolved "https://registry.yarnpkg.com/@angular-devkit/build-ng-packagr/-/build-ng-packagr-0.901.1.tgz#b024911a173921389aa70c4f4360992c85ba262f" integrity sha512-shoWppF08glJmfZV3NdZ48MMjZWETAaiZHGV/TBWzgr3O84Sl6juauqeApHZW27inpjEqyJUktp3PA7eMnpeag== @@ -105,17 +105,6 @@ "@angular-devkit/core" "9.1.1" rxjs "6.5.4" -"@angular-devkit/core@8.3.26": - version "8.3.26" - resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-8.3.26.tgz#a331f02603a64fe53b9527b07e04ee18c798b9a6" - integrity sha512-b1ng9091o33s55/cwQYh1kboiJtj8y8z8xQWATDI9kRmNIQkWYVwVa/MzgPRJ4bzbEGG3zIUHCsp52A6vuGr2A== - dependencies: - ajv "6.10.2" - fast-json-stable-stringify "2.0.0" - magic-string "0.25.3" - rxjs "6.4.0" - source-map "0.7.3" - "@angular-devkit/core@9.1.1", "@angular-devkit/core@^9.1.0": version "9.1.1" resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-9.1.1.tgz#6adb04c17d01abea506b8f2bd041aacdd569dc4f" @@ -127,15 +116,7 @@ rxjs "6.5.4" source-map "0.7.3" -"@angular-devkit/schematics@8.3.26": - version "8.3.26" - resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-8.3.26.tgz#91fcea47279a09d7504051bec17277c07f16e03b" - integrity sha512-IoZbXVFGLvVi5d0ozfssWDXuzot0/pMSKbQPzWIG8K7nCo7nNMVYpsMHrEVYUikA9EQEL5LqMCGohH36/zVPcA== - dependencies: - "@angular-devkit/core" "8.3.26" - rxjs "6.4.0" - -"@angular-devkit/schematics@9.1.1", "@angular-devkit/schematics@^9.1.0": +"@angular-devkit/schematics@9.1.1", "@angular-devkit/schematics@^9.1.1": version "9.1.1" resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-9.1.1.tgz#8ee93a6297416271002986dbf48f08ad0911a47e" integrity sha512-6wx2HcvafHvEjEa1tjDzW2hXrOiSE8ALqJUArb3+NoO1BDM42aGcqyPo0ODzKtDk12CgSsFXdNKRpQ5AmpSPtw== @@ -149,7 +130,7 @@ resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-9.1.2.tgz#01bb52d5a8813701bfacbba99338d88cfc0da9b4" integrity sha512-5UJ8SzCtFj4vZChVsni4K9oa4qE9tQ67bwnP6DKxkLEJKQWWyasYp+2siAi/7zD2ro2XA0qRMYhgQz5Vj6eBoQ== -"@angular/cdk@^9.1.0": +"@angular/cdk@^9.2.1": version "9.2.1" resolved "https://registry.yarnpkg.com/@angular/cdk/-/cdk-9.2.1.tgz#9a590b8d4671fe2fa47648297b5ea372267d238f" integrity sha512-aSG1UNPszkSnpNuDCNd7ZgT29oQ8vqHPmoqjvJI0JkEv3i6uEs5tRuhWl3TK39wDNuwdlq0AY47XTa/0Ppb5RQ== @@ -1489,7 +1470,7 @@ dependencies: any-observable "^0.3.0" -"@schematics/angular@9.1.1": +"@schematics/angular@9.1.1", "@schematics/angular@^9.1.1": version "9.1.1" resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-9.1.1.tgz#5069f8f214fa2effb68a937cfb808ec612debe96" integrity sha512-V0DcDNgHQ2YR+PGZI6+pf/mUNNxt5SusShkZ1PbwIMk/HUQpzEGkLjm3v1Jw9eIZKiuDx615GNU1xDzQ/KyNRQ== @@ -1497,14 +1478,6 @@ "@angular-devkit/core" "9.1.1" "@angular-devkit/schematics" "9.1.1" -"@schematics/angular@^8.2.1": - version "8.3.26" - resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-8.3.26.tgz#adda1552de14126e4d0993b648b4ea6258a581ce" - integrity sha512-NJCykMxB9RKL+Tmr9xHftUevsivKGsQZQKjkub528wrSgwrCWoFCxGWV31iOXkT3TlBWmuibH6MZkrWbCLX4Sw== - dependencies: - "@angular-devkit/core" "8.3.26" - "@angular-devkit/schematics" "8.3.26" - "@schematics/update@0.901.1": version "0.901.1" resolved "https://registry.yarnpkg.com/@schematics/update/-/update-0.901.1.tgz#1e28fb1a4a71a5afe99fef2fc257b1253a04dfd3" @@ -2353,16 +2326,6 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da" integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ== -ajv@6.10.2: - version "6.10.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52" - integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw== - dependencies: - fast-deep-equal "^2.0.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - ajv@6.12.0, ajv@^6.1.0, ajv@^6.10.2, ajv@^6.12.0, ajv@^6.5.5: version "6.12.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.0.tgz#06d60b96d87b8454a5adaba86e7854da629db4b7" @@ -6172,11 +6135,6 @@ fancy-log@^1.1.0, fancy-log@^1.3.2: parse-node-version "^1.0.0" time-stamp "^1.0.0" -fast-deep-equal@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" - integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= - fast-deep-equal@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" @@ -6194,11 +6152,6 @@ fast-glob@^2.2.6: merge2 "^1.2.3" micromatch "^3.1.10" -fast-json-stable-stringify@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" - integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= - fast-json-stable-stringify@2.1.0, fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -7363,6 +7316,17 @@ gulp-clean-css@^3.10.0: through2 "2.0.3" vinyl-sourcemaps-apply "0.2.1" +gulp-clean@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/gulp-clean/-/gulp-clean-0.4.0.tgz#3bc25e7084e641bbd7bde057cf90c01c50d95950" + integrity sha512-DARK8rNMo4lHOFLGTiHEJdf19GuoBDHqGUaypz+fOhrvOs3iFO7ntdYtdpNxv+AzSJBx/JfypF0yEj9ks1IStQ== + dependencies: + fancy-log "^1.3.2" + plugin-error "^0.1.2" + rimraf "^2.6.2" + through2 "^2.0.3" + vinyl "^2.1.0" + gulp-cli@^2.0.1, gulp-cli@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/gulp-cli/-/gulp-cli-2.2.0.tgz#5533126eeb7fe415a7e3e84a297d334d5cf70ebc" @@ -8975,11 +8939,19 @@ istanbul@0.4.5: which "^1.1.1" wordwrap "^1.0.0" -jasmine-core@^3.5.0: +jasmine-core@^3.5.0, jasmine-core@~3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-3.5.0.tgz#132c23e645af96d85c8bca13c8758b18429fc1e4" integrity sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA== +jasmine@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-3.5.0.tgz#7101eabfd043a1fc82ac24e0ab6ec56081357f9e" + integrity sha512-DYypSryORqzsGoMazemIHUfMkXM7I7easFaxAvNM3Mr6Xz3Fy36TupTrAOxZWN8MVKEU5xECv22J4tUQf3uBzQ== + dependencies: + glob "^7.1.4" + jasmine-core "~3.5.0" + jest-worker@25.1.0: version "25.1.0" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-25.1.0.tgz#75d038bad6fdf58eba0d2ec1835856c497e3907a" @@ -10138,13 +10110,6 @@ madge@^3.3.0: rc "^1.2.7" walkdir "^0.4.1" -magic-string@0.25.3: - version "0.25.3" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.3.tgz#34b8d2a2c7fec9d9bdf9929a3fd81d271ef35be9" - integrity sha512-6QK0OpF/phMz0Q2AxILkX2mFhi7m+WMwTRg0LQKq/WBB0cDP4rYH3Wp4/d3OTXlrPLVJT/RFqj8tFeAR4nk8AA== - dependencies: - sourcemap-codec "^1.4.4" - magic-string@0.25.7, magic-string@^0.25.0, magic-string@^0.25.2: version "0.25.7" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" @@ -13685,13 +13650,6 @@ rxjs-tslint-rules@4.10.0: tslib "^1.8.0" tsutils "^3.0.0" -rxjs@6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.4.0.tgz#f3bb0fe7bda7fb69deac0c16f17b50b0b8790504" - integrity sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw== - dependencies: - tslib "^1.9.0" - rxjs@6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.4.tgz#e0777fe0d184cec7872df147f303572d414e211c" @@ -15809,7 +15767,7 @@ tslint-microsoft-contrib@5.2.1: dependencies: tsutils "^2.27.2 <2.29.0" -tslint@^5.18.0: +tslint@^5.20.1: version "5.20.1" resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.20.1.tgz#e401e8aeda0152bc44dd07e614034f3f80c67b7d" integrity sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg== @@ -16515,7 +16473,7 @@ vinyl@^0.5.0: clone-stats "^0.0.1" replace-ext "0.0.1" -vinyl@^2.0.0: +vinyl@^2.0.0, vinyl@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.0.tgz#d85b07da96e458d25b2ffe19fece9f2caa13ed86" integrity sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==