diff --git a/templates/Angular2Spa/package-2.4.5.json b/templates/Angular2Spa/package-2.4.5.json new file mode 100644 index 0000000..401f1a1 --- /dev/null +++ b/templates/Angular2Spa/package-2.4.5.json @@ -0,0 +1,62 @@ +{ + "name": "Angular2Spa", + "version": "0.0.0", + "scripts": { + "test": "karma start ClientApp/test/karma.conf.js" + }, + "dependencies": { + "@angular/common": "^2.4.5", + "@angular/compiler": "^2.4.5", + "@angular/core": "^2.4.5", + "@angular/forms": "^2.4.5", + "@angular/http": "^2.4.5", + "@angular/platform-browser": "^2.4.5", + "@angular/platform-browser-dynamic": "^2.4.5", + "@angular/platform-server": "^2.4.5", + "@angular/router": "^3.0.2", + "@types/node": "^6.0.42", + "angular2-platform-node": "~2.0.11", + "angular2-template-loader": "^0.6.0", + "angular2-universal": "^2.1.0-rc.1", + "angular2-universal-patch": "^0.2.1", + "angular2-universal-polyfills": "^2.1.0-rc.1", + "aspnet-prerendering": "^2.0.0", + "aspnet-webpack": "^1.0.17", + "awesome-typescript-loader": "3.0.0-beta.13 || ^3.0.0", + "bootstrap": "^3.3.7", + "css": "^2.2.1", + "css-loader": "^0.25.0", + "es6-shim": "^0.35.1", + "event-source-polyfill": "^0.0.7", + "expose-loader": "^0.7.1", + "extract-text-webpack-plugin": "^2.0.0-rc", + "file-loader": "^0.9.0", + "html-loader": "^0.4.4", + "isomorphic-fetch": "^2.2.1", + "jquery": "^2.2.1", + "json-loader": "^0.5.4", + "preboot": "^4.5.2", + "raw-loader": "^0.5.1", + "rxjs": "^5.0.1", + "style-loader": "^0.13.1", + "to-string-loader": "^1.1.5", + "typescript": "^2.0.3", + "url-loader": "^0.5.7", + "webpack": "^2.2.0", + "webpack-hot-middleware": "^2.12.2", + "webpack-merge": "^0.14.1", + "zone.js": "^0.7.6" + }, + "devDependencies": { + "@types/chai": "^3.4.34", + "@types/jasmine": "^2.5.37", + "chai": "^3.5.0", + "jasmine-core": "^2.5.2", + "karma": "^1.3.0", + "karma-chai": "^0.1.0", + "karma-chrome-launcher": "^2.0.0", + "karma-cli": "^1.0.1", + "karma-jasmine": "^1.0.2", + "karma-webpack": "^1.8.0" + } +} diff --git a/templates/package-builder/src/yeoman/app/index.ts b/templates/package-builder/src/yeoman/app/index.ts index 245eff5..7da4b7a 100644 --- a/templates/package-builder/src/yeoman/app/index.ts +++ b/templates/package-builder/src/yeoman/app/index.ts @@ -35,12 +35,21 @@ const testSpecificNpmPackages = [ type YeomanPrompt = (opt: yeoman.IPromptOptions | yeoman.IPromptOptions[], callback: (answers: any) => void) => void; const optionOrPrompt: YeomanPrompt = require('yeoman-option-or-prompt'); -const templates = [ - { value: 'angular-2', name: 'Angular 2', tests: true }, - { value: 'aurelia', name: 'Aurelia', tests: false }, - { value: 'knockout', name: 'Knockout', tests: false }, - { value: 'react', name: 'React', tests: false }, - { value: 'react-redux', name: 'React with Redux', tests: false } +interface TemplateConfig { + value: string; // Internal unique ID for Yeoman prompt + rootDir: string; // Which of the template root directories should be used + name: string; // Display name + tests: boolean; + mapFilenames?: { [pattern: string]: string | boolean }; +} + +const templates: TemplateConfig[] = [ + { value: 'angular-2', rootDir: 'angular-2', name: 'Angular 2.0.2', tests: true, mapFilenames: { '^package\\-[\\d\\.]+.json$': false } }, + { 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' } }, + { value: 'aurelia', rootDir: 'aurelia', name: 'Aurelia', tests: false }, + { value: 'knockout', rootDir: 'knockout', name: 'Knockout', tests: false }, + { value: 'react', rootDir: 'react', name: 'React', tests: false }, + { value: 'react-redux', rootDir: 'react-redux', name: 'React with Redux', tests: false } ]; // Once everyone is on .csproj-compatible tooling, we might be able to remove the global.json files and eliminate @@ -88,7 +97,7 @@ class MyGenerator extends yeoman.Base { message: 'What type of project do you want to create?', choices: sdkChoices }], firstAnswers => { - const frameworkChoice = templates.filter(t => t.value === firstAnswers.framework)[0]; + const templateConfig = templates.filter(t => t.value === firstAnswers.framework)[0]; const furtherQuestions = [{ type: 'input', name: 'name', @@ -96,7 +105,7 @@ class MyGenerator extends yeoman.Base { default: this.appname }]; - if (frameworkChoice.tests) { + if (templateConfig.tests) { furtherQuestions.unshift({ type: 'confirm', name: 'tests', @@ -109,6 +118,7 @@ class MyGenerator extends yeoman.Base { answers.framework = firstAnswers.framework; this._answers = answers; this._answers.framework = firstAnswers.framework; + this._answers.templateConfig = templateConfig; this._answers.sdkVersion = firstAnswers.sdkVersion; this._answers.namePascalCase = toPascalCase(answers.name); this._answers.projectGuid = this.options['projectguid'] || uuid.v4(); @@ -122,7 +132,8 @@ class MyGenerator extends yeoman.Base { } writing() { - const templateRoot = this.templatePath(this._answers.framework); + const templateConfig = this._answers.templateConfig as TemplateConfig; + const templateRoot = this.templatePath(templateConfig.rootDir); const chosenSdk = sdkChoices.filter(sdk => sdk.value === this._answers.sdkVersion)[0]; glob.sync('**/*', { cwd: templateRoot, dot: true, nodir: true }).forEach(fn => { // Token replacement in filenames @@ -133,12 +144,22 @@ class MyGenerator extends yeoman.Base { outputFn = path.join(path.dirname(fn), '.gitignore'); } + // Perform any filename replacements configured for the template + const mappedFilename = applyFirstMatchingReplacement(outputFn, templateConfig.mapFilenames); + let fileIsExcludedByTemplateConfig = false; + if (typeof mappedFilename === 'string') { + outputFn = mappedFilename; + } else { + fileIsExcludedByTemplateConfig = true; + } + // Decide whether to emit this file const isTestSpecificFile = testSpecificPaths.some(regex => regex.test(outputFn)); const isSdkSpecificFile = sdkChoices.some(sdk => sdk.includeFiles.some(regex => regex.test(outputFn))); const matchesChosenSdk = chosenSdk.includeFiles.some(regex => regex.test(outputFn)); const emitFile = (matchesChosenSdk || !isSdkSpecificFile) - && (this._answers.tests || !isTestSpecificFile); + && (this._answers.tests || !isTestSpecificFile) + && !fileIsExcludedByTemplateConfig; if (emitFile) { let inputFullPath = path.join(templateRoot, fn); @@ -247,5 +268,28 @@ function rewritePackageJson(contents, includeTests) { return contents; } +function applyFirstMatchingReplacement(inputValue: string, replacements: { [pattern: string]: string | boolean }): string | boolean { + if (replacements) { + const replacementPatterns = Object.getOwnPropertyNames(replacements); + for (let patternIndex = 0; patternIndex < replacementPatterns.length; patternIndex++) { + const pattern = replacementPatterns[patternIndex]; + const regexp = new RegExp(pattern); + if (regexp.test(inputValue)) { + const replacement = replacements[pattern]; + + // To avoid bug-prone evaluation order dependencies, we only respond to the first name match per file + if (typeof (replacement) === 'boolean') { + return replacement; + } else { + return inputValue.replace(regexp, replacement); + } + } + } + } + + // No match + return inputValue; +} + declare var module: any; (module).exports = MyGenerator;