@@ -35,12 +35,21 @@ const testSpecificNpmPackages = [
35
35
type YeomanPrompt = ( opt : yeoman . IPromptOptions | yeoman . IPromptOptions [ ] , callback : ( answers : any ) => void ) => void ;
36
36
const optionOrPrompt : YeomanPrompt = require ( 'yeoman-option-or-prompt' ) ;
37
37
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 }
44
53
] ;
45
54
46
55
// 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 {
88
97
message : 'What type of project do you want to create?' ,
89
98
choices : sdkChoices
90
99
} ] , firstAnswers => {
91
- const frameworkChoice = templates . filter ( t => t . value === firstAnswers . framework ) [ 0 ] ;
100
+ const templateConfig = templates . filter ( t => t . value === firstAnswers . framework ) [ 0 ] ;
92
101
const furtherQuestions = [ {
93
102
type : 'input' ,
94
103
name : 'name' ,
95
104
message : 'Your project name' ,
96
105
default : this . appname
97
106
} ] ;
98
107
99
- if ( frameworkChoice . tests ) {
108
+ if ( templateConfig . tests ) {
100
109
furtherQuestions . unshift ( {
101
110
type : 'confirm' ,
102
111
name : 'tests' ,
@@ -109,6 +118,7 @@ class MyGenerator extends yeoman.Base {
109
118
answers . framework = firstAnswers . framework ;
110
119
this . _answers = answers ;
111
120
this . _answers . framework = firstAnswers . framework ;
121
+ this . _answers . templateConfig = templateConfig ;
112
122
this . _answers . sdkVersion = firstAnswers . sdkVersion ;
113
123
this . _answers . namePascalCase = toPascalCase ( answers . name ) ;
114
124
this . _answers . projectGuid = this . options [ 'projectguid' ] || uuid . v4 ( ) ;
@@ -122,7 +132,8 @@ class MyGenerator extends yeoman.Base {
122
132
}
123
133
124
134
writing ( ) {
125
- const templateRoot = this . templatePath ( this . _answers . framework ) ;
135
+ const templateConfig = this . _answers . templateConfig as TemplateConfig ;
136
+ const templateRoot = this . templatePath ( templateConfig . rootDir ) ;
126
137
const chosenSdk = sdkChoices . filter ( sdk => sdk . value === this . _answers . sdkVersion ) [ 0 ] ;
127
138
glob . sync ( '**/*' , { cwd : templateRoot , dot : true , nodir : true } ) . forEach ( fn => {
128
139
// Token replacement in filenames
@@ -133,12 +144,22 @@ class MyGenerator extends yeoman.Base {
133
144
outputFn = path . join ( path . dirname ( fn ) , '.gitignore' ) ;
134
145
}
135
146
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
+
136
156
// Decide whether to emit this file
137
157
const isTestSpecificFile = testSpecificPaths . some ( regex => regex . test ( outputFn ) ) ;
138
158
const isSdkSpecificFile = sdkChoices . some ( sdk => sdk . includeFiles . some ( regex => regex . test ( outputFn ) ) ) ;
139
159
const matchesChosenSdk = chosenSdk . includeFiles . some ( regex => regex . test ( outputFn ) ) ;
140
160
const emitFile = ( matchesChosenSdk || ! isSdkSpecificFile )
141
- && ( this . _answers . tests || ! isTestSpecificFile ) ;
161
+ && ( this . _answers . tests || ! isTestSpecificFile )
162
+ && ! fileIsExcludedByTemplateConfig ;
142
163
143
164
if ( emitFile ) {
144
165
let inputFullPath = path . join ( templateRoot , fn ) ;
@@ -247,5 +268,28 @@ function rewritePackageJson(contents, includeTests) {
247
268
return contents ;
248
269
}
249
270
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
+
250
294
declare var module : any ;
251
295
( module ) . exports = MyGenerator ;
0 commit comments