diff --git a/templates/yeoman/package.json b/templates/yeoman/package.json index 093df60..982cb80 100644 --- a/templates/yeoman/package.json +++ b/templates/yeoman/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "build": "tsc && node ./built/CreateGenerator.js" + "build": "tsc && node ./tmp/build/build.js" }, "author": "Microsoft", "license": "Apache-2.0", diff --git a/templates/yeoman/src/CreateGenerator.ts b/templates/yeoman/src/CreateGenerator.ts deleted file mode 100644 index df2bd62..0000000 --- a/templates/yeoman/src/CreateGenerator.ts +++ /dev/null @@ -1,48 +0,0 @@ -import * as glob from 'glob'; -import * as gitignore from 'gitignore-parser'; -import * as fs from 'fs'; -import * as path from 'path'; -import * as _ from 'lodash'; -import * as mkdirp from 'mkdirp'; -import * as rimraf from 'rimraf'; - -const textFileExtensions = ['.gitignore', '.config', '.cs', '.cshtml', 'Dockerfile', '.html', '.js', '.json', '.jsx', '.md', '.ts', '.tsx']; - -const templates = { - 'angular-2': '../../templates/Angular2Spa/', - 'knockout': '../../templates/KnockoutSpa/', - 'react-redux': '../../templates/ReactReduxSpa/', - 'react': '../../templates/ReactSpa/' -}; - -function isTextFile(filename: string): boolean { - return textFileExtensions.indexOf(path.extname(filename).toLowerCase()) >= 0; -} - -function writeFileEnsuringDirExists(root: string, filename: string, contents: string | Buffer) { - let fullPath = path.join(root, filename); - mkdirp.sync(path.dirname(fullPath)); - fs.writeFileSync(fullPath, contents); -} - -function listFilesExcludingGitignored(root: string): string[] { - let gitignoreEvaluator = gitignore.compile(fs.readFileSync(path.join(root, '.gitignore'), 'utf8')); - return glob.sync('**/*', { cwd: root, dot: true, nodir: true }) - .filter(fn => gitignoreEvaluator.accepts(fn)); -} - -function writeTemplate(sourceRoot: string, destRoot: string) { - listFilesExcludingGitignored(sourceRoot).forEach(fn => { - const sourceContent = fs.readFileSync(path.join(sourceRoot, fn)); - writeFileEnsuringDirExists(destRoot, fn, sourceContent); - }); -} - -const outputRoot = './generator-aspnet-spa'; -const commonRoot = path.join(outputRoot, 'templates/common'); -rimraf.sync(outputRoot); - -_.forEach(templates, (templateRootDir, templateName) => { - const outputDir = path.join(outputRoot, 'templates', templateName); - writeTemplate(templateRootDir, outputDir); -}); diff --git a/templates/yeoman/src/build/build.ts b/templates/yeoman/src/build/build.ts new file mode 100644 index 0000000..af05496 --- /dev/null +++ b/templates/yeoman/src/build/build.ts @@ -0,0 +1,96 @@ +import * as glob from 'glob'; +import * as gitignore from 'gitignore-parser'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as _ from 'lodash'; +import * as mkdirp from 'mkdirp'; +import * as rimraf from 'rimraf'; + +const textFileExtensions = ['.gitignore', '.config', '.cs', '.cshtml', 'Dockerfile', '.html', '.js', '.json', '.jsx', '.md', '.ts', '.tsx', '.xproj']; + +const templates = { + 'angular-2': '../../templates/Angular2Spa/', + 'knockout': '../../templates/KnockoutSpa/', + 'react-redux': '../../templates/ReactReduxSpa/', + 'react': '../../templates/ReactSpa/' +}; + +const contentReplacements: { from: RegExp, to: string }[] = [ + { from: /\bWebApplicationBasic\b/g, to: '<%= namePascalCase %>' }, + { from: /[0-9a-f\-]{36}<\/ProjectGuid>/g, to: '<%= projectGuid %>' }, + { from: /.*?<\/RootNamespace>/g, to: '<%= namePascalCase %>'}, + { from: /\s*/g, to: '' }, + { from: /\s*/g, to: '' }, +]; + +const filenameReplacements: { from: RegExp, to: string }[] = [ + { from: /.*\.xproj$/, to: 'tokenreplace-namePascalCase.xproj' } +]; + +function isTextFile(filename: string): boolean { + return textFileExtensions.indexOf(path.extname(filename).toLowerCase()) >= 0; +} + +function writeFileEnsuringDirExists(root: string, filename: string, contents: string | Buffer) { + let fullPath = path.join(root, filename); + mkdirp.sync(path.dirname(fullPath)); + fs.writeFileSync(fullPath, contents); +} + +function listFilesExcludingGitignored(root: string): string[] { + let gitIgnorePath = path.join(root, '.gitignore'); + let gitignoreEvaluator = fs.existsSync(gitIgnorePath) + ? gitignore.compile(fs.readFileSync(gitIgnorePath, 'utf8')) + : { accepts: () => true }; + return glob.sync('**/*', { cwd: root, dot: true, nodir: true }) + .filter(fn => gitignoreEvaluator.accepts(fn)); +} + +function writeTemplate(sourceRoot: string, destRoot: string) { + listFilesExcludingGitignored(sourceRoot).forEach(fn => { + let sourceContent = fs.readFileSync(path.join(sourceRoot, fn)); + + // For text files, replace hardcoded values with template tags + if (isTextFile(fn)) { + let sourceText = sourceContent.toString('utf8'); + contentReplacements.forEach(replacement => { + sourceText = sourceText.replace(replacement.from, replacement.to); + }); + + sourceContent = new Buffer(sourceText, 'utf8'); + } + + // Also apply replacements in filenames + filenameReplacements.forEach(replacement => { + fn = fn.replace(replacement.from, replacement.to); + }); + + writeFileEnsuringDirExists(destRoot, fn, sourceContent); + }); +} + +function copyRecursive(sourceRoot: string, destRoot: string, matchGlob: string) { + glob.sync(matchGlob, { cwd: sourceRoot, dot: true, nodir: true }) + .forEach(fn => { + const sourceContent = fs.readFileSync(path.join(sourceRoot, fn)); + writeFileEnsuringDirExists(destRoot, fn, sourceContent); + }); +} + +const outputRoot = './generator-aspnet-spa'; +const outputTemplatesRoot = path.join(outputRoot, 'app/templates'); +rimraf.sync(outputTemplatesRoot); + +// Copy template files +_.forEach(templates, (templateRootDir, templateName) => { + const outputDir = path.join(outputTemplatesRoot, templateName); + writeTemplate(templateRootDir, outputDir); +}); + +// Also copy the generator files (that's the compiled .js files, plus all other non-.ts files) +const tempRoot = './tmp'; +copyRecursive(path.join(tempRoot, 'generator'), outputRoot, '**/*.js'); +copyRecursive('./src/generator', outputRoot, '**/!(*.ts)'); + +// Clean up +rimraf.sync(tempRoot); diff --git a/templates/yeoman/src/generator/app/index.ts b/templates/yeoman/src/generator/app/index.ts new file mode 100644 index 0000000..72cb41c --- /dev/null +++ b/templates/yeoman/src/generator/app/index.ts @@ -0,0 +1,58 @@ +import * as path from 'path'; +import * as yeoman from 'yeoman-generator'; +import * as uuid from 'node-uuid'; +import * as glob from 'glob'; +const toPascalCase = require('to-pascal-case'); + +const templates = [ + { value: 'angular-2', name: 'Angular 2' }, + { value: 'knockout', name: 'Knockout' }, + { value: 'react', name: 'React' }, + { value: 'react-redux', name: 'React with Redux' } +]; + +class MyGenerator extends yeoman.Base { + private _answers: any; + + constructor(args: string | string[], options: any) { + super(args, options); + } + + prompting() { + const done = this.async(); + + this.prompt([{ + type: 'list', + name: 'framework', + message: 'Framework', + choices: templates + }, { + type: 'input', + name: 'name', + message: 'Your project name', + default: this.appname + }], answers => { + this._answers = answers; + this._answers.namePascalCase = toPascalCase(answers.name); + this._answers.projectGuid = uuid.v4(); + done(); + }) + } + + writing() { + var templateRoot = this.templatePath(this._answers.framework); + glob.sync('**/*', { cwd: templateRoot, dot: true, nodir: true }).forEach(fn => { + // Token replacement in filenames + let outputFn = fn.replace(/tokenreplace\-([^\.\/]*)/g, (substr, token) => this._answers[token]); + + this.fs.copyTpl( + path.join(templateRoot, fn), + this.destinationPath(outputFn), + this._answers + ); + }); + } +} + +declare var module: any; +(module).exports = MyGenerator; diff --git a/templates/yeoman/src/generator/package.json b/templates/yeoman/src/generator/package.json new file mode 100644 index 0000000..c0abb65 --- /dev/null +++ b/templates/yeoman/src/generator/package.json @@ -0,0 +1,10 @@ +{ + "name": "generator-aspnet-spa", + "version": "0.1.0", + "description": "Single-Page App templates for ASP.NET Core", + "files": ["templates"], + "keywords": ["yeoman-generator"], + "dependencies": { + "yeoman-generator": "^0.20.2" + } +} diff --git a/templates/yeoman/tsconfig.json b/templates/yeoman/tsconfig.json index 5b89f17..6cc3875 100644 --- a/templates/yeoman/tsconfig.json +++ b/templates/yeoman/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "moduleResolution": "node", "target": "es5", - "outDir": "built", + "outDir": "tmp", "sourceMap": false }, "exclude": [ diff --git a/templates/yeoman/tsd.json b/templates/yeoman/tsd.json index a2db2d2..6b9c72b 100644 --- a/templates/yeoman/tsd.json +++ b/templates/yeoman/tsd.json @@ -9,7 +9,7 @@ "commit": "544a35a10866b32afda9c7f029c0764558563f4f" }, "node/node.d.ts": { - "commit": "544a35a10866b32afda9c7f029c0764558563f4f" + "commit": "08ed4e9f1869e37e29514d862e0158b40e550232" }, "glob/glob.d.ts": { "commit": "544a35a10866b32afda9c7f029c0764558563f4f" @@ -25,6 +25,15 @@ }, "rimraf/rimraf.d.ts": { "commit": "544a35a10866b32afda9c7f029c0764558563f4f" + }, + "node-uuid/node-uuid.d.ts": { + "commit": "08ed4e9f1869e37e29514d862e0158b40e550232" + }, + "node-uuid/node-uuid-base.d.ts": { + "commit": "08ed4e9f1869e37e29514d862e0158b40e550232" + }, + "node-uuid/node-uuid-cjs.d.ts": { + "commit": "08ed4e9f1869e37e29514d862e0158b40e550232" } } } diff --git a/templates/yeoman/typings/node-uuid/node-uuid-base.d.ts b/templates/yeoman/typings/node-uuid/node-uuid-base.d.ts new file mode 100644 index 0000000..fc59e9c --- /dev/null +++ b/templates/yeoman/typings/node-uuid/node-uuid-base.d.ts @@ -0,0 +1,46 @@ +// Type definitions for node-uuid.js +// Project: https://github.com/broofa/node-uuid +// Definitions by: Jeff May +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +/** Common definitions for all environments */ +declare namespace __NodeUUID { + interface UUIDOptions { + + /** + * Node id as Array of 6 bytes (per 4.1.6). + * Default: Randomly generated ID. See note 1. + */ + node?: any[]; + + /** + * (Number between 0 - 0x3fff) RFC clock sequence. + * Default: An internally maintained clockseq is used. + */ + clockseq?: number; + + /** + * (Number | Date) Time in milliseconds since unix Epoch. + * Default: The current time is used. + */ + msecs?: number|Date; + + /** + * (Number between 0-9999) additional time, in 100-nanosecond units. Ignored if msecs is unspecified. + * Default: internal uuid counter is used, as per 4.2.1.2. + */ + nsecs?: number; + } + + interface UUID { + v1(options?: UUIDOptions): string; + v1(options?: UUIDOptions, buffer?: number[], offset?: number): number[]; + + v4(options?: UUIDOptions): string; + v4(options?: UUIDOptions, buffer?: number[], offset?: number): number[]; + + parse(id: string, buffer?: number[], offset?: number): number[]; + + unparse(buffer: number[], offset?: number): string; + } +} diff --git a/templates/yeoman/typings/node-uuid/node-uuid-cjs.d.ts b/templates/yeoman/typings/node-uuid/node-uuid-cjs.d.ts new file mode 100644 index 0000000..78f7535 --- /dev/null +++ b/templates/yeoman/typings/node-uuid/node-uuid-cjs.d.ts @@ -0,0 +1,15 @@ +// Type definitions for node-uuid.js +// Project: https://github.com/broofa/node-uuid +// Definitions by: Jeff May +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +/// + +/** + * Expose as CommonJS module + * For use in node environment or browser environment (using webpack or other module loaders) + */ +declare module "node-uuid" { + var uuid: __NodeUUID.UUID; + export = uuid; +} \ No newline at end of file diff --git a/templates/yeoman/typings/node-uuid/node-uuid.d.ts b/templates/yeoman/typings/node-uuid/node-uuid.d.ts new file mode 100644 index 0000000..a673705 --- /dev/null +++ b/templates/yeoman/typings/node-uuid/node-uuid.d.ts @@ -0,0 +1,36 @@ +// Type definitions for node-uuid.js +// Project: https://github.com/broofa/node-uuid +// Definitions by: Jeff May +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +/// +/// +/// + +/** + * Definitions for use in node environment + * + * !! For browser enviroments, use node-uuid-global or node-uuid-cjs + */ +declare module __NodeUUID { + /** + * Overloads for node environment + * We need to duplicate some declarations because + * interface merging doesn't work with overloads + */ + interface UUID { + v1(options?: UUIDOptions): string; + v1(options?: UUIDOptions, buffer?: number[], offset?: number): number[]; + v1(options?: UUIDOptions, buffer?: Buffer, offset?: number): Buffer; + + v4(options?: UUIDOptions): string; + v4(options?: UUIDOptions, buffer?: number[], offset?: number): number[]; + v4(options?: UUIDOptions, buffer?: Buffer, offset?: number): Buffer; + + parse(id: string, buffer?: number[], offset?: number): number[]; + parse(id: string, buffer?: Buffer, offset?: number): Buffer; + + unparse(buffer: number[], offset?: number): string; + unparse(buffer: Buffer, offset?: number): string; + } +} diff --git a/templates/yeoman/typings/node/node.d.ts b/templates/yeoman/typings/node/node.d.ts index 1b2da31..6f11575 100644 --- a/templates/yeoman/typings/node/node.d.ts +++ b/templates/yeoman/typings/node/node.d.ts @@ -679,6 +679,8 @@ declare module "cluster" { kill(signal?: string): void; destroy(signal?: string): void; disconnect(): void; + isConnected(): boolean; + isDead(): boolean; } export var settings: ClusterSettings; @@ -688,7 +690,9 @@ declare module "cluster" { export function fork(env?: any): Worker; export function disconnect(callback?: Function): void; export var worker: Worker; - export var workers: Worker[]; + export var workers: { + [index: string]: Worker + }; // Event emitter export function addListener(event: string, listener: Function): void; diff --git a/templates/yeoman/typings/tsd.d.ts b/templates/yeoman/typings/tsd.d.ts index c099e00..dbcb962 100644 --- a/templates/yeoman/typings/tsd.d.ts +++ b/templates/yeoman/typings/tsd.d.ts @@ -5,3 +5,6 @@ /// /// /// +/// +/// +///