import { normalize } from '@angular-devkit/core' ; import * as path from 'path' ; import { SchematicContext , Tree } from '@angular-devkit/schematics' ; import { WorkspaceSchema , WorkspaceProject } from '@schematics/angular/utility/workspace-models' ; import { execSync } from 'child_process' ; import { Attribute , Comment , Element , Expansion , ExpansionCase , HtmlParser , HtmlTagDefinition , Node , Text , Visitor } from '@angular/compiler' ; import { replaceMatch } from './tsUtils' ; const configPaths = [ '/.angular.json' , '/angular.json' ] ; export const getProjectPaths = ( config ) : string [ ] => { const sourceDirs = [ ] ; const projects = getProjects ( config ) ; for ( const proj of projects ) { const sourcePath = path . join ( '/' , proj . sourceRoot ) ; sourceDirs . push ( normalize ( sourcePath ) ) ; } return sourceDirs ; } ; export const getWorkspacePath = ( host ) : string => configPaths . find ( x => host . exists ( x ) ) ; export const getWorkspace = ( host ) : WorkspaceSchema => { const configPath = getWorkspacePath ( host ) ; if ( configPath ) { return JSON . parse ( host . read ( configPath ) . toString ( ) ) ; } return null ; } ; export const getProjects = ( config ) : WorkspaceProject [ ] => { const projects = [ ] ; for ( const projName of Object . keys ( config . projects ) ) { const proj = config . projects [ projName ] ; if ( ( proj . architect && proj . architect . e2e && ! proj . architect . build ) ) { continue ; } projects . push ( proj ) ; } return projects ; } ; export const escapeRegExp = ( string ) => string . replace ( /[.*+?^${}()|[\]\\]/g , '\\$&' ) ; export const supports = ( name ) : boolean => { try { execSync ( `${ name } --version` , { stdio : 'ignore' } ) ; return true ; } catch { return false ; } } ; export const getPackageManager = ( host ) => { const hasYarn = supports ( 'yarn' ) ; const hasYarnLock = host . exists ( 'yarn.lock' ) ; if ( hasYarn && hasYarnLock ) { return 'yarn' ; } return 'npm' ; } ; export const canResolvePackage = ( pkg ) : boolean => { try { execSync ( `node -e "require.resolve('${ pkg }');"` , { stdio : 'ignore' } ) ; return true ; } catch { return false ; } } ; export const getPackageVersion = ( pkg ) : string => { let version = null ; try { version = require ( path . posix . join ( pkg , 'package.json' ) ) ?. version ; } catch { } return version ; } ; export const tryInstallPackage = ( context , packageManager , pkg ) => { try { context . logger . debug ( `Installing ${ pkg } via ${ packageManager }.` ) ; switch ( packageManager ) { case 'yarn' : execSync ( `${ packageManager } add ${ pkg } --no-lock-file` , { stdio : 'ignore' } ) ; break ; case 'npm' : execSync ( `${ packageManager } i ${ pkg } --no-save --no-audit` , { stdio : 'ignore' } ) ; break ; } context . logger . debug ( `${ pkg } installed successfully.` ) ; } catch ( e ) { context . logger . warn ( `Could not install ${ pkg }.` , JSON . parse ( e ) ) ; } } ; export const tryUninstallPackage = ( context , packageManager , pkg ) => { try { context . logger . debug ( `Uninstalling ${ pkg } via ${ packageManager }` ) ; switch ( packageManager ) { case 'yarn' : execSync ( `${ packageManager } remove ${ pkg }` , { stdio : 'ignore' } ) ; break ; case 'npm' : execSync ( `${ packageManager } uninstall ${ pkg } --no-save` , { stdio : 'ignore' } ) ; break ; } context . logger . debug ( `${ pkg } uninstalled successfully.` ) ; } catch ( e ) { context . logger . warn ( `Could not uninstall ${ pkg }, you may want to uninstall it manually.` , JSON . parse ( e ) ) ; } } ; interface TagOffset { start : number ; end : number ; } export interface SourceOffset { startTag : TagOffset ; endTag : TagOffset ; file : { content : string ; url : string ; } ; node ? : Element ; } export class FileChange { constructor ( public position = 0 , public text = '' , public replaceText = '' , public type = 'insert' ) { } public apply ( content ) { if ( this . type === 'insert' ) { return `${ content . substring ( 0 , this . position ) }${ this . text }${ content . substring ( this . position ) }` ; } return replaceMatch ( content , this . replaceText , this . text , this . position ) ; } } param host param filePath param encoding export const parseFile = ( parser , host , filePath , encoding = 'utf8' ) => parser . parse ( host . read ( filePath ) . toString ( encoding ) , filePath ) . rootNodes ; export const findElementNodes = ( root , tag ) : Node [ ] => { const tags = new Set ( Array . isArray ( tag ) ? tag : [ tag ] ) ; return flatten ( Array . isArray ( root ) ? root : [ root ] ) . filter ( ( node ) => tags . has ( node . name ) ) ; } ; export const hasAttribute = ( root , attribute ) => { const attrs = Array . isArray ( attribute ) ? attribute : [ attribute ] ; return ! ! root . attrs . find ( a => attrs . includes ( a . name ) ) ; } ; export const getAttribute = ( root , attribute ) => { const attrs = Array . isArray ( attribute ) ? attribute : [ attribute ] ; return root . attrs . filter ( a => attrs . includes ( a . name ) ) ; } ; export const getSourceOffset = ( element ) : SourceOffset => { const { startSourceSpan , endSourceSpan } = element ; return { startTag : { start : startSourceSpan . start . offset , end : startSourceSpan . end . offset } , endTag : { start : endSourceSpan ?. start . offset , end : endSourceSpan ?. end . offset } , file : { content : startSourceSpan . start . file . content , url : startSourceSpan . start . file . url } , node : element } ; } ; const isElement = ( node ) : boolean => ( node as Element ) . children !== undefined ; param list export const flatten = ( list ) => { let r = [ ] ; for ( const node of list ) { r . push ( node ) ; if ( isElement ( node ) ) { r = r . concat ( flatten ( ( node as Element ) . children ) ) ; } } return r ; } ; /**
 * https://github.com/angular/angular/blob/master/packages/compiler/test/ml_parser/util/util.ts
 *
 * May be useful for validating the output of our own migrations,
 */ class SerializerVisitor implements Visitor { /**
     *
     */ constructor ( private getHtmlTagDefinition ) { } public visitElement ( element , _context ) { if ( this . getHtmlTagDefinition ( element . name ) . isVoid ) { return `<${ element . name }${ this . _visitAll ( element . attrs , ' ' ) }/>` ; } return `<${ element . name }${ this . _visitAll ( element . attrs , ' ' ) }>${ this . _visitAll ( element . children ) }</${ element . name }>` ; } public visitAttribute ( attribute , _context ) { return attribute . value === '' ? `${ attribute . name }` : `${ attribute . name }="${ attribute . value }"` ; } public visitText ( text , _context ) { return text . value ; } public visitComment ( comment , _context ) { return `<!--${ comment . value }-->` ; } public visitExpansion ( expansion , _context ) { return `{${ expansion . switchValue }, ${ expansion . type },${ this . _visitAll ( expansion . cases ) }}` ; } public visitExpansionCase ( expansionCase , _context ) { return ` ${ expansionCase . value } {${ this . _visitAll ( expansionCase . expression ) }}` ; } private _visitAll ( nodes , join = '' ) { if ( nodes . length === 0 ) { return '' ; } return join + nodes . map ( a => a . visit ( this , null ) ) . join ( join ) ; } } export const serializeNodes = ( nodes , getHtmlTagDefinition ) : string [ ] => { return nodes . map ( node => node . visit ( new SerializerVisitor ( getHtmlTagDefinition ) , null ) ) ; } ; export const makeNgIf = ( name , value ) => name . startsWith ( '[' ) && value !== 'true' ; export const stringifyAttriutes = ( attributes ) => { let stringAttributes = '' ; attributes . forEach ( element => { stringAttributes = stringAttributes . concat ( element . name . includes ( '#' ) ? ` ${ element . name } ` : `${ element . name }="${ element . value }" ` ) ; } ) ; return stringAttributes ; } ;