import { Rule , SchematicContext , Tree } from '@angular-devkit/schematics' ; import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks' ; import * as colors from 'ansi-colors' ; export const packageJsonConfigPath = './package.json' ; export const ObliquePackage = '@oblique/oblique' ; const glob = require ( 'glob' ) ; const angularJsonConfigPath = './angular.json/' ; export let isSuccessful = true ; export function error ( msg ) { throw new Error ( `${ colors . symbols . cross } Migration failed: ${ msg }\n` ) ; } export function infoText ( context , msg ) { context . logger . info ( colors . cyanBright ( `\n${ colors . symbols . info } ${ msg }\n` ) ) ; } export function infoMigration ( context , msg ) { context . logger . info ( `${ colors . symbols . pointer } ${ msg }` ) ; } export function success ( context , msg ) { context . logger . info ( colors . greenBright ( `\n${ colors . symbols . check } ${ msg }\n` ) ) ; } export function warn ( context , msg ) { context . logger . info ( colors . yellowBright ( `\n${ colors . symbols . warning } ${ msg }\n` ) ) ; } export function createSafeRule ( callback ) { return ( tree , context ) => { try { return callback ( tree , context ) ; } catch ( error ) { isSuccessful = false ; const groups = /@oblique[/\\]oblique[/\\]schematics[/\\].*[/\\](?<file>\w*\.js):(?<line>\d*)/ . exec ( error . stack || '' ) ?. groups || { } ; warn ( context , `The previous task failed and the change needs to be done manually.\nPlease inform the Oblique team (oblique@bit.admin.ch) of the following error:\n\t${ error . message || error }, in "${ groups . file }" on line ${ groups . line }` ) ; return tree ; } } ; } export function infoHighlights ( context , msg , ... highlights ) { const message = highlights . reduce ( ( text , highlight ) => text . replace ( '%c' , colors . bold ( highlight ) ) , msg ) ; context . logger . info ( `${ message }\n` ) ; } export function readFile ( tree , fileName ) { const src = tree . read ( fileName ) ; return src ? src . toString ( ) : '' ; } export function addFile ( tree , fileName , content ) { if ( ! tree . exists ( fileName ) && content ) { tree . create ( fileName , content ) ; } } export function writeFile ( tree , fileName , content ) { if ( content ) { if ( tree . exists ( fileName ) && content ) { tree . overwrite ( fileName , content ) ; } else { tree . create ( fileName , content ) ; } } } export function deleteFile ( tree , fileName ) { if ( tree . exists ( fileName ) ) { tree . delete ( fileName ) ; } return tree ; } export function replaceInFile ( tree , path , pattern , replacement ) { tree . overwrite ( path , readFile ( tree , path ) . replace ( pattern , replacement ) ) ; } export function getJson ( tree , path ) { const json = readFile ( tree , path ) ; return json ? JSON . parse ( json . toString ( ) ) : undefined ; } export function getAngularConfigs ( tree , path ) { const json = getJson ( tree , angularJsonConfigPath ) ; return Object . keys ( getJsonProperty ( json , 'projects' ) ) . reduce ( ( config , project ) => [ ... config , { project , config : getJsonProperty ( json , [ 'projects' , project , ... path ] . join ( ';' ) ) } ] , [ ] ) . filter ( project => project . config ) ; } export function checkIfAngularConfigExists ( tree , path , config ) { return getAngularConfigs ( tree , path ) . reduce ( ( exists , conf ) => exists || conf . config === config , false ) ; } export function getDefaultAngularConfig ( tree , path ) { const json = getJson ( tree , angularJsonConfigPath ) ; const defaultProjectName = getJsonProperty ( json , 'defaultProject' ) ; return getJsonProperty ( json , [ 'projects' , defaultProjectName , ... path ] . join ( ';' ) ) ; } export function setRootAngularConfig ( tree , path , value ) { const json = getJson ( tree , angularJsonConfigPath ) ; setOption ( json , path , value ) ; tree . overwrite ( angularJsonConfigPath , JSON . stringify ( json , null , 2 ) ) ; return tree ; } export function setAngularConfig ( tree , path , value ) { return alterAngularConfig ( tree , path , value . project , value . config ) ; } export function setAngularProjectsConfig ( tree , path , config ) { getAngularConfigs ( tree , path ) . forEach ( project => { setAngularConfig ( tree , path , { project : project . project , config : config instanceof Function ? config ( project . config ) : config } ) ; } ) ; return tree ; } export function addAngularConfigInList ( tree , path , value ) { getAngularConfigs ( tree , path ) . forEach ( project => setAngularConfig ( tree , path , { project : project . project , config : [ ... ( project . config || [ ] ) . filter ( ( v ) => v !== value ) , value ] } ) ) ; return tree ; } export function removeAngularProjectsConfig ( tree , path ) { getAngularConfigs ( tree , path ) . forEach ( project => removeAngularConfig ( tree , path , project . project ) ) ; return tree ; } export function installDependencies ( ) { return createSafeRule ( ( tree , _context ) => { _context . addTask ( new NodePackageInstallTask ( ) ) ; _context . logger . debug ( 'Dependencies installed' ) ; return tree ; } ) ; } export function applyInTree ( tree , toApply , pattern = '*' ) { getAngularConfigs ( tree , [ 'sourceRoot' ] ) . map ( project => project . config ) . reduce ( ( files , root ) => [ ... files , ... glob . sync ( `${ root }/**/${ pattern }` , { } ) ] , [ ] ) . forEach ( ( file ) => toApply ( file ) ) ; return tree ; } export function addInterface ( tree , fileName , name ) { const content = readFile ( tree , fileName ) ; if ( ! new RegExp ( `export class\\s*\\w*\\s*implements.*${ name }` , 'm' ) . test ( content ) ) { tree . overwrite ( fileName , content . replace ( /(export class\s*\w*(?:\s*extends \w*)?)(?:\s*implements\s*)?(\w*(?:,\s*\w*)*)\s*{/ , `$1 implements $2, ${ name } {` ) . replace ( 'implements ,' , 'implements' ) ) ; } } export function addImport ( tree , fileName , name , pkg ) { const content = readFile ( tree , fileName ) ; if ( ! hasImport ( content , name , pkg ) ) { tree . overwrite ( fileName , new RegExp ( `import\\s*{.*}\\s*from\\s*['"]${ pkg }['"]` , 'm' ) . test ( content ) ? content . replace ( new RegExp ( `import\\s*{(.*)}\\s*from\\s*['"]${ pkg }['"]` ) , `import {$1, ${ name }} from '${ pkg }'` ) : `import {${ name }} from '${ pkg }';\n${ content }` ) ; } } export function removeImport ( tree , fileName , name , pkg ) { const content = readFile ( tree , fileName ) ; if ( hasImport ( content , name , pkg ) ) { tree . overwrite ( fileName , new RegExp ( `import\\s*{\\s*${ name }\\s*}\\s*from\\s*['"]${ pkg }['"]` , 'm' ) . test ( content ) ? content . replace ( new RegExp ( `import\\s*{\\s*${ name }\\s*}\\s*from\\s*['"]${ pkg }['"]\\s*;\\s*` ) , '' ) : content . replace ( new RegExp ( `(import\\s*{\\s*.*)${ name }(?:,\\s*)?(.*\\s*}\\s*from\\s*['"]${ pkg }['"]\\s*;\\s*)` ) , '$1$2' ) . replace ( /,\s*}/ , '}' ) ) ; } } function hasImport ( content , name , pkg ) { return new RegExp ( `import\\s*{\\s*.*${ name }.*from\\s*['"]${ pkg }['"]` , 'm' ) . test ( content ) ; } function getJsonProperty ( json , propertyPath ) { return propertyPath . split ( ';' ) . reduce ( ( obj , property ) => ( obj ? obj [ property ] : undefined ) , json ) ; } function removeAngularConfig ( tree , path , project ) { return alterAngularConfig ( tree , path , project ) ; } function alterAngularConfig ( tree , path , project , value ? ) { const json = getJson ( tree , angularJsonConfigPath ) ; setOption ( json , [ 'projects' , project , ... path ] , value ) ; tree . overwrite ( angularJsonConfigPath , JSON . stringify ( json , null , 2 ) ) ; return tree ; } function setOption ( json , path , value ? ) { const option = path . shift ( ) ; if ( option ) { if ( path . length ) { if ( ! json [ option ] ) { json [ option ] = { } ; } setOption ( json [ option ] , path , value ) ; } else { if ( value ) { json [ option ] = value ; } else { delete json [ option ] ; } } } }