import { strings } from '@angular-devkit/core' ; import { apply , MergeStrategy , mergeWith , Rule , SchematicContext , SchematicsException , TaskConfigurationGenerator , TaskExecutor , TaskExecutorFactory , template , Tree , url } from '@angular-devkit/schematics' ; import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks' ; import { NodePackageInstallTaskOptions } from '@angular-devkit/schematics/tasks/package-manager/install-task' ; import { spawn , SpawnOptions } from 'child_process' ; import decomment = require ( 'decomment' ) ; import { Observable } from 'rxjs' ; import ts = require ( 'typescript' ) ; import { addProviderToModule } from '@schematics/angular/utility/ast-utils' ; import { InsertChange } from '@schematics/angular/utility/change' ; import { ANGULAR_JSON , NG_ESSENTIALS , PACKAGE_JSON , TSCONFIG_JSON } from './constants' ; import { NgEssentialsOptions } from './ng-essentials/schema' ; export type DependencyType = 'dependencies' | 'devDependencies' | 'resolutions' ; export type AppOrLibType = 'app' | 'lib' ; export type TsConfigContext = 'app' | 'spec' | 'lib' ; export function removeAutomaticUpdateSymbols ( ) { return ( host ) => { const sourceText = host . read ( PACKAGE_JSON ) . toString ( 'utf-8' ) ; const packageJson = JSON . parse ( sourceText ) ; for ( const index in packageJson . dependencies ) { packageJson . dependencies [ index ] = packageJson . dependencies [ index ] . replace ( '^' , '' ) ; packageJson . dependencies [ index ] = packageJson . dependencies [ index ] . replace ( '~' , '' ) ; packageJson . dependencies [ index ] = packageJson . dependencies [ index ] . replace ( '>=' , '' ) ; } for ( const index in packageJson . devDependencies ) { packageJson . devDependencies [ index ] = packageJson . devDependencies [ index ] . replace ( '^' , '' ) ; packageJson . devDependencies [ index ] = packageJson . devDependencies [ index ] . replace ( '~' , '' ) ; packageJson . devDependencies [ index ] = packageJson . devDependencies [ index ] . replace ( '>=' , '' ) ; } host . overwrite ( PACKAGE_JSON , JSON . stringify ( packageJson , null , 2 ) ) ; return host ; } ; } export function removePackageFromPackageJson ( type , pkg ) { return ( host ) => { const sourceText = host . read ( PACKAGE_JSON ) . toString ( 'utf-8' ) ; const packageJson = JSON . parse ( sourceText ) ; if ( packageJson [ type ] [ pkg ] ) { delete packageJson [ type ] [ pkg ] ; host . overwrite ( PACKAGE_JSON , JSON . stringify ( packageJson , null , 2 ) ) ; } return host ; } ; } export function addPackageToPackageJson ( type , pkg , version ) { return ( host ) => { const sourceText = host . read ( PACKAGE_JSON ) . toString ( 'utf-8' ) ; const packageJson = JSON . parse ( sourceText ) ; if ( ! packageJson [ type ] ) { packageJson [ type ] = { } ; } if ( ! packageJson [ type ] [ pkg ] ) { packageJson [ type ] [ pkg ] = version ; } if ( ! packageJson [ type ] [ pkg ] [ version ] ) { packageJson [ type ] [ pkg ] = version ; } host . overwrite ( PACKAGE_JSON , JSON . stringify ( packageJson , null , 2 ) ) ; return host ; } ; } export function removeScriptFromPackageJson ( key ) { return ( host ) => { const sourceText = host . read ( PACKAGE_JSON ) . toString ( 'utf-8' ) ; const packageJson = JSON . parse ( sourceText ) ; if ( packageJson [ 'scripts' ] [ key ] ) { delete packageJson [ 'scripts' ] [ key ] ; } host . overwrite ( PACKAGE_JSON , JSON . stringify ( packageJson , null , 2 ) ) ; return host ; } ; } export function addScriptToPackageJson ( key , command ) { return ( host ) => { const sourceText = host . read ( PACKAGE_JSON ) . toString ( 'utf-8' ) ; const packageJson = JSON . parse ( sourceText ) ; packageJson [ 'scripts' ] = { ... Object . keys ( packageJson [ 'scripts' ] ) . filter ( ( existingKey ) => existingKey !== key ) . reduce ( ( obj , key ) => { obj [ key ] = packageJson [ 'scripts' ] [ key ] ; return obj ; } , { } ) , [ key ] : command } ; host . overwrite ( PACKAGE_JSON , JSON . stringify ( packageJson , null , 2 ) ) ; return host ; } ; } export function findDefaultProjectNameInAngularJson ( host ) { const sourceText = host . read ( ANGULAR_JSON ) . toString ( 'utf-8' ) ; const angularJson = JSON . parse ( sourceText ) ; const defaulProjectName = angularJson [ 'defaultProject' ] ; return defaulProjectName ? defaulProjectName : '' ; } export function findNewProjectRootInAngularJson ( host ) { const sourceText = host . read ( ANGULAR_JSON ) . toString ( 'utf-8' ) ; const angularJson = JSON . parse ( sourceText ) ; const newProjectRoot = angularJson [ 'newProjectRoot' ] ; return newProjectRoot ? newProjectRoot : 'projects' ; } export function findElementPrefixInAngularJson ( host , projectName ) { const sourceText = host . read ( ANGULAR_JSON ) . toString ( 'utf-8' ) ; const angularJson = JSON . parse ( sourceText ) ; const elementPrefix = angularJson [ 'projects' ] [ projectName ] [ 'prefix' ] ; return elementPrefix ? elementPrefix : 'app' ; } export function findJestOptionInAngularJson ( host ) { const sourceText = host . read ( ANGULAR_JSON ) . toString ( 'utf-8' ) ; const angularJson = JSON . parse ( sourceText ) ; const optionsFromAngularJson = angularJson [ 'schematics' ] [ NG_ESSENTIALS ] ; if ( optionsFromAngularJson ) { return optionsFromAngularJson . jest ; } return false ; } export function removeArchitectNodeFromAngularJson ( applicationName , node ) { return ( host ) => { const sourceText = host . read ( ANGULAR_JSON ) . toString ( 'utf-8' ) ; const angularJson = JSON . parse ( sourceText ) ; if ( angularJson [ 'projects' ] [ applicationName ] [ 'architect' ] [ node ] ) { delete angularJson [ 'projects' ] [ applicationName ] [ 'architect' ] [ node ] ; } host . overwrite ( ANGULAR_JSON , JSON . stringify ( angularJson , null , 2 ) ) ; return host ; } ; } export function tsconfigFilePath ( rootPath , context ) { return `${ rootPath }/tsconfig.${ context }.json` ; } export function updateJson < T = any , O = T > ( filePath , callback ) { return ( host ) => { if ( ! host . exists ( filePath ) ) { return host ; } const fileContent = host . read ( filePath ) . toString ( 'utf-8' ) ; const json = JSON . parse ( decomment ( fileContent ) ) ; host . overwrite ( filePath , JSON . stringify ( callback ( json ) , null , 2 ) ) ; return host ; } ; } export function deleteFile ( file ) { return ( host ) => { if ( host . exists ( file ) ) { host . delete ( file ) ; } return host ; } ; } export function copyConfigFiles ( path , options = undefined ) { return mergeWith ( apply ( url ( path ) , [ template ( { ... options , utils : strings , dot : '.' , tmpl : '' } ) ] ) , MergeStrategy . Overwrite ) ; } export function updateDevelopmentEnvironmentFile ( sourceDirectory ) { return ( host ) => { host . overwrite ( `${ sourceDirectory }/environments/environment.ts` , `const providers: any[] = [
  { provide: 'environment', useValue: 'Development' },
  { provide: 'baseUrl', useValue: 'http://localhost:3000' }
];

export const ENV_PROVIDERS = providers;

export const environment = {
  production: false
};
` ) ; return host ; } ; } export function updateProductionEnvironmentFile ( sourceDirectory ) { return ( host ) => { host . overwrite ( `${ sourceDirectory }/environments/environment.prod.ts` , `const providers: any[] = [
  { provide: 'environment', useValue: 'Production' },
  { provide: 'baseUrl', useValue: 'http://localhost:3000' }
];

export const ENV_PROVIDERS = providers;

export const environment = {
  production: true
};
` ) ; return host ; } ; } export function addEnvProvidersToAppModule ( sourceDirectory ) { return ( host ) => { const modulePath = `${ sourceDirectory }/app/app.module.ts` ; const text = host . read ( modulePath ) ; if ( ! text ) { throw new SchematicsException ( `File ${ modulePath } does not exist.` ) ; } const sourceText = text . toString ( 'utf-8' ) ; const source = ts . createSourceFile ( modulePath , sourceText , ts . ScriptTarget . Latest , true ) ; const changes = addProviderToModule ( source , modulePath , 'ENV_PROVIDERS' , '../environments/environment' ) ; const recorder = host . beginUpdate ( modulePath ) ; for ( const change of changes ) { if ( change instanceof InsertChange ) { recorder . insertLeft ( change . pos , change . toAdd ) ; } } host . commitUpdate ( recorder ) ; return host ; } ; } export function removeEndToEndTestFiles ( applicationPath = '' ) { if ( applicationPath && ! applicationPath . endsWith ( '/' ) ) { applicationPath = applicationPath + '/' ; } return ( host ) => { host . delete ( `${ applicationPath }e2e/src/app.e2e-spec.ts` ) ; host . delete ( `${ applicationPath }e2e/src/app.po.ts` ) ; host . delete ( `${ applicationPath }e2e/protractor.conf.js` ) ; host . delete ( `${ applicationPath }e2e/tsconfig.json` ) ; } ; } export function runNpmPackageInstall ( ) { return ( host , context ) => { context . addTask ( new NodePackageInstallTask ( ) ) ; return host ; } ; } export function installPackage ( packageName ) { return ( host , context ) => { const options = new NodePackageInstallTaskOptions ( ) ; options . packageName = packageName ; context . addTask ( new NodePackageInstallTask ( ) ) ; return host ; } ; } let taskRegistered = false ; export function runNpmScript ( scriptName , ... args ) { return ( _host , context ) => { if ( ! context . engine . workflow ) { return ; } if ( ! taskRegistered ) { const engineHost = ( context . engine . workflow as any ) . _engineHost ; engineHost . registerTaskExecutor ( createRunNpmScript ( ) ) ; taskRegistered = true ; } ( context . engine as any ) . _taskSchedulers . forEach ( ( scheduler ) => { if ( scheduler . _queue . peek ( ) && scheduler . _queue . peek ( ) . configuration . name === 'RunNpmScript' && scheduler . _queue . peek ( ) . configuration . options . scriptName === scriptName ) { scheduler . _queue . pop ( ) ; } } ) ; context . addTask ( new RunNpmScript ( scriptName , args ) ) ; } ; } interface NpmScriptOptions { scriptName : string ; args : string [ ] ; } class RunNpmScript implements TaskConfigurationGenerator < NpmScriptOptions > { constructor ( private taskName , private args ) { } toConfiguration ( ) { return { name : 'RunNpmScript' , options : { scriptName : this . taskName , args : this . args } } ; } } function createRunNpmScript ( ) { return { name : 'RunNpmScript' , create : ( ) => { return Promise . resolve < TaskExecutor < NpmScriptOptions > > ( ( options , _context ) => { const args = [ ] ; args . push ( 'run' ) ; args . push ( options . scriptName ) ; args . push ( ... options . args ) ; const outputStream = process . stdout ; const errorStream = process . stderr ; const spawnOptions = { stdio : [ process . stdin , outputStream , errorStream ] , shell : true , cwd : process . cwd ( ) } ; return new Observable ( ( obs ) => { spawn ( 'npm' , args , spawnOptions ) . on ( 'close' , ( code ) => { if ( code === 0 ) { obs . next ( ) ; obs . complete ( ) ; } else { const message = 'npm task failed, see above.' ; obs . error ( new Error ( message ) ) ; } } ) ; } ) ; } ) ; } } ; }