@@ -35,12 +35,21 @@ const testSpecificNpmPackages = [
3535type YeomanPrompt = ( opt : yeoman . IPromptOptions | yeoman . IPromptOptions [ ] , callback : ( answers : any ) => void ) => void ;
3636const optionOrPrompt : YeomanPrompt = require ( 'yeoman-option-or-prompt' ) ;
3737
38- const templates = [
39- { value : 'angular-2' , name : 'Angular 2' , tests : true } ,
40- { value : 'aurelia' , name : 'Aurelia' , tests : false } ,
41- { value : 'knockout' , name : 'Knockout' , tests : false } ,
42- { value : 'react' , name : 'React' , tests : false } ,
43- { value : 'react-redux' , name : 'React with Redux' , tests : false }
38+ interface TemplateConfig {
39+ value : string ; // Internal unique ID for Yeoman prompt
40+ rootDir : string ; // Which of the template root directories should be used
41+ name : string ; // Display name
42+ tests : boolean ;
43+ mapFilenames ?: { [ pattern : string ] : string | boolean } ;
44+ }
45+
46+ const templates : TemplateConfig [ ] = [
47+ { value : 'angular-2' , rootDir : 'angular-2' , name : 'Angular 2.0.2' , tests : true , mapFilenames : { '^package\\-[\\d\\.]+.json$' : false } } ,
48+ { value : 'angular-245' , rootDir : 'angular-2' , name : 'Angular 2.4.5 (experimental)' , tests : true , mapFilenames : { '^package.json$' : false , '^package\\-2\\.4\\.5.json$' : 'package.json' } } ,
49+ { value : 'aurelia' , rootDir : 'aurelia' , name : 'Aurelia' , tests : false } ,
50+ { value : 'knockout' , rootDir : 'knockout' , name : 'Knockout' , tests : false } ,
51+ { value : 'react' , rootDir : 'react' , name : 'React' , tests : false } ,
52+ { value : 'react-redux' , rootDir : 'react-redux' , name : 'React with Redux' , tests : false }
4453] ;
4554
4655// Once everyone is on .csproj-compatible tooling, we might be able to remove the global.json files and eliminate
@@ -88,15 +97,15 @@ class MyGenerator extends yeoman.Base {
8897 message : 'What type of project do you want to create?' ,
8998 choices : sdkChoices
9099 } ] , firstAnswers => {
91- const frameworkChoice = templates . filter ( t => t . value === firstAnswers . framework ) [ 0 ] ;
100+ const templateConfig = templates . filter ( t => t . value === firstAnswers . framework ) [ 0 ] ;
92101 const furtherQuestions = [ {
93102 type : 'input' ,
94103 name : 'name' ,
95104 message : 'Your project name' ,
96105 default : this . appname
97106 } ] ;
98107
99- if ( frameworkChoice . tests ) {
108+ if ( templateConfig . tests ) {
100109 furtherQuestions . unshift ( {
101110 type : 'confirm' ,
102111 name : 'tests' ,
@@ -109,6 +118,7 @@ class MyGenerator extends yeoman.Base {
109118 answers . framework = firstAnswers . framework ;
110119 this . _answers = answers ;
111120 this . _answers . framework = firstAnswers . framework ;
121+ this . _answers . templateConfig = templateConfig ;
112122 this . _answers . sdkVersion = firstAnswers . sdkVersion ;
113123 this . _answers . namePascalCase = toPascalCase ( answers . name ) ;
114124 this . _answers . projectGuid = this . options [ 'projectguid' ] || uuid . v4 ( ) ;
@@ -122,7 +132,8 @@ class MyGenerator extends yeoman.Base {
122132 }
123133
124134 writing ( ) {
125- const templateRoot = this . templatePath ( this . _answers . framework ) ;
135+ const templateConfig = this . _answers . templateConfig as TemplateConfig ;
136+ const templateRoot = this . templatePath ( templateConfig . rootDir ) ;
126137 const chosenSdk = sdkChoices . filter ( sdk => sdk . value === this . _answers . sdkVersion ) [ 0 ] ;
127138 glob . sync ( '**/*' , { cwd : templateRoot , dot : true , nodir : true } ) . forEach ( fn => {
128139 // Token replacement in filenames
@@ -133,12 +144,22 @@ class MyGenerator extends yeoman.Base {
133144 outputFn = path . join ( path . dirname ( fn ) , '.gitignore' ) ;
134145 }
135146
147+ // Perform any filename replacements configured for the template
148+ const mappedFilename = applyFirstMatchingReplacement ( outputFn , templateConfig . mapFilenames ) ;
149+ let fileIsExcludedByTemplateConfig = false ;
150+ if ( typeof mappedFilename === 'string' ) {
151+ outputFn = mappedFilename ;
152+ } else {
153+ fileIsExcludedByTemplateConfig = true ;
154+ }
155+
136156 // Decide whether to emit this file
137157 const isTestSpecificFile = testSpecificPaths . some ( regex => regex . test ( outputFn ) ) ;
138158 const isSdkSpecificFile = sdkChoices . some ( sdk => sdk . includeFiles . some ( regex => regex . test ( outputFn ) ) ) ;
139159 const matchesChosenSdk = chosenSdk . includeFiles . some ( regex => regex . test ( outputFn ) ) ;
140160 const emitFile = ( matchesChosenSdk || ! isSdkSpecificFile )
141- && ( this . _answers . tests || ! isTestSpecificFile ) ;
161+ && ( this . _answers . tests || ! isTestSpecificFile )
162+ && ! fileIsExcludedByTemplateConfig ;
142163
143164 if ( emitFile ) {
144165 let inputFullPath = path . join ( templateRoot , fn ) ;
@@ -247,5 +268,28 @@ function rewritePackageJson(contents, includeTests) {
247268 return contents ;
248269}
249270
271+ function applyFirstMatchingReplacement ( inputValue : string , replacements : { [ pattern : string ] : string | boolean } ) : string | boolean {
272+ if ( replacements ) {
273+ const replacementPatterns = Object . getOwnPropertyNames ( replacements ) ;
274+ for ( let patternIndex = 0 ; patternIndex < replacementPatterns . length ; patternIndex ++ ) {
275+ const pattern = replacementPatterns [ patternIndex ] ;
276+ const regexp = new RegExp ( pattern ) ;
277+ if ( regexp . test ( inputValue ) ) {
278+ const replacement = replacements [ pattern ] ;
279+
280+ // To avoid bug-prone evaluation order dependencies, we only respond to the first name match per file
281+ if ( typeof ( replacement ) === 'boolean' ) {
282+ return replacement ;
283+ } else {
284+ return inputValue . replace ( regexp , replacement ) ;
285+ }
286+ }
287+ }
288+ }
289+
290+ // No match
291+ return inputValue ;
292+ }
293+
250294declare var module : any ;
251295( module ) . exports = MyGenerator ;
0 commit comments