Beginning Yeoman generator generator. Currently creates templates in as patches vs a common set of files.

This commit is contained in:
SteveSandersonMS
2016-04-28 14:28:34 +01:00
parent 5534f1bd54
commit 1830c20212
16 changed files with 22301 additions and 0 deletions

3
templates/yeoman/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/node_modules/
/built/
/generator-aspnet-spa/

View File

@@ -0,0 +1,5 @@
To generator the Yeoman generator, execute:
npm run build
Output will appear in the `generator-aspnet-spa` directory.

View File

@@ -0,0 +1,20 @@
{
"name": "generator-aspnetspa-generator",
"version": "1.0.0",
"description": "Creates the Yeoman generator for ASP.NET Core SPA templates",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc && node ./built/CreateGenerator.js"
},
"author": "Microsoft",
"license": "Apache-2.0",
"dependencies": {
"diff": "^2.2.2",
"gitignore-parser": "0.0.2",
"glob": "^7.0.3",
"lodash": "^4.11.1",
"mkdirp": "^0.5.1",
"rimraf": "^2.5.2"
}
}

View File

@@ -0,0 +1,95 @@
import * as yeoman from 'yeoman-generator';
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 diff from 'diff';
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 writeCommonFiles(outDir: string) {
let filesByTemplate = _.mapValues(templates, listFilesExcludingGitignored);
let commonFiles = _.intersection.apply(_, _.values(filesByTemplate));
commonFiles.forEach(fn => {
let templateRoots = _.values(templates);
let origContent = fs.readFileSync(path.join(templateRoots[0], fn));
if (isTextFile(fn)) {
// For text files, we copy the portion that's common to all the templates
let commonText = origContent.toString('utf8');
templateRoots.slice(1).forEach(otherTemplateRoot => {
let otherTemplateContent = fs.readFileSync(path.join(otherTemplateRoot, fn), 'utf8');
commonText = diff.diffLines(commonText, otherTemplateContent)
.filter(c => !(c.added || c.removed))
.map(c => c.value)
.join('');
});
writeFileEnsuringDirExists(outDir, fn, commonText);
} else {
// For binary (or maybe-binary) files, we only consider them common if they are identical across all templates
let isIdenticalEverywhere = !templateRoots.slice(1).some(otherTemplateRoot => {
return !fs.readFileSync(path.join(otherTemplateRoot, fn)).equals(origContent);
});
if (isIdenticalEverywhere) {
writeFileEnsuringDirExists(outDir, fn, origContent);
}
}
});
}
function writeDiffsForTemplate(sourceRoot: string, destRoot: string, commonRoot: string) {
listFilesExcludingGitignored(sourceRoot).forEach(fn => {
const commonFn = path.join(commonRoot, fn);
const sourceContent = fs.readFileSync(path.join(sourceRoot, fn));
if (!fs.existsSync(commonFn)) {
// This file is unique to this template - just copy as-is
writeFileEnsuringDirExists(destRoot, fn, sourceContent);
} else {
let commonText = fs.readFileSync(commonFn, 'utf8');
let sourceText = sourceContent.toString('utf8');
if (commonText !== sourceText) {
// Write a diff vs the common version of this file
let fileDiff = diff.createPatch(fn, commonText, sourceText, null, null);
writeFileEnsuringDirExists(destRoot, fn + '.patch', fileDiff);
}
}
});
}
const outputRoot = './generator-aspnet-spa';
const commonRoot = path.join(outputRoot, 'templates/common');
rimraf.sync(outputRoot);
writeCommonFiles(commonRoot);
_.forEach(templates, (templateRootDir, templateName) => {
writeDiffsForTemplate(templateRootDir, path.join(outputRoot, 'templates', templateName), commonRoot);
});

View File

@@ -0,0 +1,12 @@
{
"compilerOptions": {
"moduleResolution": "node",
"target": "es5",
"outDir": "built",
"sourceMap": false
},
"exclude": [
"node_modules",
"generator-aspnet-spa"
]
}

33
templates/yeoman/tsd.json Normal file
View File

@@ -0,0 +1,33 @@
{
"version": "v4",
"repo": "borisyankov/DefinitelyTyped",
"ref": "master",
"path": "typings",
"bundle": "typings/tsd.d.ts",
"installed": {
"yeoman-generator/yeoman-generator.d.ts": {
"commit": "544a35a10866b32afda9c7f029c0764558563f4f"
},
"node/node.d.ts": {
"commit": "544a35a10866b32afda9c7f029c0764558563f4f"
},
"glob/glob.d.ts": {
"commit": "544a35a10866b32afda9c7f029c0764558563f4f"
},
"minimatch/minimatch.d.ts": {
"commit": "544a35a10866b32afda9c7f029c0764558563f4f"
},
"lodash/lodash.d.ts": {
"commit": "544a35a10866b32afda9c7f029c0764558563f4f"
},
"diff/diff.d.ts": {
"commit": "544a35a10866b32afda9c7f029c0764558563f4f"
},
"mkdirp/mkdirp.d.ts": {
"commit": "544a35a10866b32afda9c7f029c0764558563f4f"
},
"rimraf/rimraf.d.ts": {
"commit": "544a35a10866b32afda9c7f029c0764558563f4f"
}
}
}

60
templates/yeoman/typings/diff/diff.d.ts vendored Normal file
View File

@@ -0,0 +1,60 @@
// Type definitions for diff
// Project: https://github.com/kpdecker/jsdiff
// Definitions by: vvakame <https://github.com/vvakame/>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
declare namespace JsDiff {
interface IDiffResult {
value: string;
count?: number;
added?: boolean;
removed?: boolean;
}
interface IBestPath {
newPos: number;
componenets: IDiffResult[];
}
class Diff {
ignoreWhitespace:boolean;
constructor(ignoreWhitespace?:boolean);
diff(oldString:string, newString:string):IDiffResult[];
pushComponent(components:IDiffResult[], value:string, added:boolean, removed:boolean):void;
extractCommon(basePath:IBestPath, newString:string, oldString:string, diagonalPath:number):number;
equals(left:string, right:string):boolean;
join(left:string, right:string):string;
tokenize(value:string):any; // return types are string or string[]
}
function diffChars(oldStr:string, newStr:string):IDiffResult[];
function diffWords(oldStr:string, newStr:string):IDiffResult[];
function diffWordsWithSpace(oldStr:string, newStr:string):IDiffResult[];
function diffJson(oldObj: Object, newObj: Object): IDiffResult[];
function diffLines(oldStr:string, newStr:string):IDiffResult[];
function diffCss(oldStr:string, newStr:string):IDiffResult[];
function createPatch(fileName:string, oldStr:string, newStr:string, oldHeader:string, newHeader:string):string;
function applyPatch(oldStr:string, uniDiff:string):string;
function convertChangesToXML(changes:IDiffResult[]):string;
function convertChangesToDMP(changes:IDiffResult[]):{0: number; 1:string;}[];
}
declare module "diff" {
export = JsDiff;
}

View File

@@ -0,0 +1,3 @@
declare module 'gitignore-parser' {
export function compile(gitignoreContents: string): any;
}

112
templates/yeoman/typings/glob/glob.d.ts vendored Normal file
View File

@@ -0,0 +1,112 @@
// Type definitions for Glob 5.0.10
// Project: https://github.com/isaacs/node-glob
// Definitions by: vvakame <https://github.com/vvakame/>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
/// <reference path="../node/node.d.ts" />
/// <reference path="../minimatch/minimatch.d.ts" />
declare module "glob" {
import events = require("events");
import fs = require('fs');
import minimatch = require("minimatch");
function G(pattern: string, cb: (err: Error, matches: string[]) => void): void;
function G(pattern: string, options: G.IOptions, cb: (err: Error, matches: string[]) => void): void;
namespace G {
function sync(pattern: string, options?: IOptions): string[];
function hasMagic(pattern: string, options?: IOptions): boolean;
var Glob: IGlobStatic;
var GlobSync: IGlobSyncStatic;
interface IOptions extends minimatch.IOptions {
cwd?: string;
root?: string;
dot?: boolean;
nomount?: boolean;
mark?: boolean;
nosort?: boolean;
stat?: boolean;
silent?: boolean;
strict?: boolean;
cache?: { [path: string]: any /* boolean | string | string[] */ };
statCache?: { [path: string]: fs.Stats };
symlinks?: any;
sync?: boolean;
nounique?: boolean;
nonull?: boolean;
debug?: boolean;
nobrace?: boolean;
noglobstar?: boolean;
noext?: boolean;
nocase?: boolean;
matchBase?: any;
nodir?: boolean;
ignore?: any; /* string | string[] */
follow?: boolean;
realpath?: boolean;
nonegate?: boolean;
nocomment?: boolean;
/** Deprecated. */
globDebug?: boolean;
}
interface IGlobStatic extends events.EventEmitter {
new (pattern: string, cb?: (err: Error, matches: string[]) => void): IGlob;
new (pattern: string, options: IOptions, cb?: (err: Error, matches: string[]) => void): IGlob;
prototype: IGlob;
}
interface IGlobSyncStatic {
new (pattern: string, options?: IOptions): IGlobBase
prototype: IGlobBase;
}
interface IGlobBase {
minimatch: minimatch.IMinimatch;
options: IOptions;
aborted: boolean;
cache: { [path: string]: any /* boolean | string | string[] */ };
statCache: { [path: string]: fs.Stats };
symlinks: { [path: string]: boolean };
realpathCache: { [path: string]: string };
found: string[];
}
interface IGlob extends IGlobBase, events.EventEmitter {
pause(): void;
resume(): void;
abort(): void;
/** Deprecated. */
EOF: any;
/** Deprecated. */
paused: boolean;
/** Deprecated. */
maxDepth: number;
/** Deprecated. */
maxLength: number;
/** Deprecated. */
changedCwd: boolean;
/** Deprecated. */
cwd: string;
/** Deprecated. */
root: string;
/** Deprecated. */
error: any;
/** Deprecated. */
matches: string[];
/** Deprecated. */
log(...args: any[]): void;
/** Deprecated. */
emitMatch(m: any): void;
}
}
export = G;
}

19265
templates/yeoman/typings/lodash/lodash.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,64 @@
// Type definitions for Minimatch 2.0.8
// Project: https://github.com/isaacs/minimatch
// Definitions by: vvakame <https://github.com/vvakame/>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
declare module "minimatch" {
function M(target: string, pattern: string, options?: M.IOptions): boolean;
namespace M {
function match(list: string[], pattern: string, options?: IOptions): string[];
function filter(pattern: string, options?: IOptions): (element: string, indexed: number, array: string[]) => boolean;
function makeRe(pattern: string, options?: IOptions): RegExp;
var Minimatch: IMinimatchStatic;
interface IOptions {
debug?: boolean;
nobrace?: boolean;
noglobstar?: boolean;
dot?: boolean;
noext?: boolean;
nocase?: boolean;
nonull?: boolean;
matchBase?: boolean;
nocomment?: boolean;
nonegate?: boolean;
flipNegate?: boolean;
}
interface IMinimatchStatic {
new (pattern: string, options?: IOptions): IMinimatch;
prototype: IMinimatch;
}
interface IMinimatch {
pattern: string;
options: IOptions;
/** 2-dimensional array of regexp or string expressions. */
set: any[][]; // (RegExp | string)[][]
regexp: RegExp;
negate: boolean;
comment: boolean;
empty: boolean;
makeRe(): RegExp; // regexp or boolean
match(fname: string): boolean;
matchOne(files: string[], pattern: string[], partial: boolean): boolean;
/** Deprecated. For internal use. */
debug(): void;
/** Deprecated. For internal use. */
make(): void;
/** Deprecated. For internal use. */
parseNegate(): void;
/** Deprecated. For internal use. */
braceExpand(pattern: string, options: IOptions): void;
/** Deprecated. For internal use. */
parse(pattern: string, isSub?: boolean): void;
}
}
export = M;
}

View File

@@ -0,0 +1,15 @@
// Type definitions for mkdirp 0.3.0
// Project: http://github.com/substack/node-mkdirp
// Definitions by: Bart van der Schoor <https://github.com/Bartvds>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
declare module 'mkdirp' {
function mkdirp(dir: string, cb: (err: any, made: string) => void): void;
function mkdirp(dir: string, flags: any, cb: (err: any, made: string) => void): void;
namespace mkdirp {
function sync(dir: string, flags?: any): string;
}
export = mkdirp;
}

2340
templates/yeoman/typings/node/node.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,16 @@
// Type definitions for rimraf
// Project: https://github.com/isaacs/rimraf
// Definitions by: Carlos Ballesteros Velasco <https://github.com/soywiz>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// Imported from: https://github.com/soywiz/typescript-node-definitions/rimraf.d.ts
declare module "rimraf" {
function rimraf(path: string, callback: (error: Error) => void): void;
namespace rimraf {
export function sync(path: string): void;
export var EMFILE_MAX: number;
export var BUSYTRIES_MAX: number;
}
export = rimraf;
}

8
templates/yeoman/typings/tsd.d.ts vendored Normal file
View File

@@ -0,0 +1,8 @@
/// <reference path="node/node.d.ts" />
/// <reference path="yeoman-generator/yeoman-generator.d.ts" />
/// <reference path="glob/glob.d.ts" />
/// <reference path="minimatch/minimatch.d.ts" />
/// <reference path="lodash/lodash.d.ts" />
/// <reference path="diff/diff.d.ts" />
/// <reference path="mkdirp/mkdirp.d.ts" />
/// <reference path="rimraf/rimraf.d.ts" />

View File

@@ -0,0 +1,250 @@
// Type definitions for yeoman-generator
// Project: https://github.com/yeoman/generator
// Definitions by: Kentaro Okuno <http://github.com/armorik83>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
/// <reference path="../node/node.d.ts" />
declare namespace yo {
export interface IYeomanGenerator {
argument(name: string, config: IArgumentConfig): void;
composeWith(namespace: string, options: any, settings?: IComposeSetting): IYeomanGenerator;
defaultFor(name: string): void;
destinationRoot(rootPath?: string): string;
destinationPath(...path: string[]): string;
determineAppname(): void;
getCollisionFilter(): (output: any) => void;
hookFor(name: string, config: IHookConfig): void;
option(name: string, config: IYeomanGeneratorOption): void;
rootGeneratorName(): string;
run(args?: any): void;
run(args: any, callback?: Function): void;
runHooks(callback?: Function): void;
sourceRoot(rootPath?: string): string;
templatePath(...path: string[]): string;
prompt(opt: IPromptOptions | IPromptOptions[], callback: (answers: any) => void): void;
npmInstall(packages?: string[] | string, options?: any, cb?: Function): void;
installDependencies(options?: IInstallDependencyOptions): void;
spawnCommand(name: string, args?: string[], options?: Object): void;
spawnCommandSync(name: string, args?: string[], options?: Object): void;
options: { [key: string]: any };
fs: IMemFsEditor;
}
export class YeomanGeneratorBase implements IYeomanGenerator, NodeJS.EventEmitter {
argument(name: string, config: IArgumentConfig): void;
composeWith(namespace: string, options: any, settings?: IComposeSetting): IYeomanGenerator;
defaultFor(name: string): void;
destinationRoot(rootPath?: string): string;
destinationPath(...path: string[]): string;
determineAppname(): void;
getCollisionFilter(): (output: any) => void;
hookFor(name: string, config: IHookConfig): void;
option(name: string, config?: IYeomanGeneratorOption): void;
rootGeneratorName(): string;
run(args?: any): void;
run(args: any, callback?: Function): void;
runHooks(callback?: Function): void;
sourceRoot(rootPath?: string): string;
templatePath(...path: string[]): string;
addListener(event: string, listener: Function): this;
on(event: string, listener: Function): this;
once(event: string, listener: Function): this;
removeListener(event: string, listener: Function): this;
removeAllListeners(event?: string): this;
setMaxListeners(n: number): this;
getMaxListeners(): number;
listeners(event: string): Function[];
emit(event: string, ...args: any[]): boolean;
listenerCount(type: string): number;
async(): any;
prompt(opt: IPromptOptions | IPromptOptions[], callback: (answers: any) => void): void;
log(message: string) : void;
npmInstall(packages: string[], options?: any, cb?: Function) :void;
installDependencies(options?: IInstallDependencyOptions): void;
spawnCommand(name: string, args?: string[], options?: Object): void;
spawnCommandSync(name: string, args?: string[], options?: Object): void;
appname: string;
gruntfile: IGruntFileStatic;
options: { [key: string]: any };
fs: IMemFsEditor;
}
export interface IMemFsEditor {
read(filepath: string, options?: Object): string;
readJSON(filepath: string, defaults?: Object): Object;
write(filepath: string, contents: string): void;
writeJSON(filepath: string, contents: Object, replacer?: Function, space?: number): void;
delete(filepath: string, options?: Object): void;
copy(from: string, to: string, options?: Object): void;
copyTpl(from: string, to: string, context: Object, options?: Object): void;
move(from: string, to: string, options?: Object): void;
exists(filepath: string): boolean;
commit(callback: Function): void;
commit(filters: any[], callback: Function): void;
}
export interface IInstallDependencyOptions {
npm?: boolean;
bower?: boolean;
skipMessage?: boolean;
callback?: Function;
}
export interface IChoice {
name: string;
value: string;
short?: string;
}
export interface IPromptOptions{
type?: string;
name: string;
message: string | ((answers: Object) => string);
choices?: any[] | ((answers: Object) => any);
default?: string | number | string[] | number[] | ((answers: Object) => (string | number | string[] | number[]));
validate?: ((input: any) => boolean | string);
filter?: ((input: any) => any);
when?: ((answers: Object) => boolean) | boolean;
store?: boolean;
}
export interface IGruntFileStatic {
loadNpmTasks(pluginName: string): void;
insertConfig(name:string, config:any):void;
registerTask(name:string, tasks:any):void;
insertVariable(name:string, value:any):void;
prependJavaScript(code:string):void;
}
export interface IArgumentConfig {
desc: string;
required?: boolean;
optional?: boolean;
type: any;
defaults?: any;
}
export interface IComposeSetting {
local?: string;
link?: string;
}
export interface IHookConfig {
as: string;
args: any;
options: any;
}
export interface IYeomanGeneratorOption {
alias?: string;
defaults?: any;
desc?: string;
hide?: boolean;
type?: any;
}
export interface IQueueProps {
initializing: () => void;
prompting?: () => void;
configuring?: () => void;
default?: () => void;
writing: {
[target: string]: () => void;
};
conflicts?: () => void;
install?: () => void;
end: () => void;
}
export interface INamedBase extends IYeomanGenerator {
}
export interface IBase extends INamedBase {
}
export interface IAssert {
file(path: string): void;
file(paths: string[]): void;
fileContent(file: string, reg: RegExp): void;
/** @param {[String, RegExp][]} pairs */
fileContent(pairs: any[][]): void;
/** @param {[String, RegExp][]|String[]} pairs */
files(pairs: any[]): void;
/**
* @param {Object} subject
* @param {Object|Array} methods
*/
implement(subject: any, methods: any): void;
noFile(file: string): void;
noFileContent(file: string, reg: RegExp): void;
/** @param {[String, RegExp][]} pairs */
noFileContent(pairs: any[][]): void;
/**
* @param {Object} subject
* @param {Object|Array} methods
*/
noImplement(subject: any, methods: any): void;
textEqual(value: string, expected: string): void;
}
export interface ITestHelper {
createDummyGenerator(): IYeomanGenerator;
createGenerator(name: string, dependencies: any[], args: any, options: any): IYeomanGenerator;
decorate(context: any, method: string, replacement: Function, options: any): void;
gruntfile(options: any, done: Function): void;
mockPrompt(generator: IYeomanGenerator, answers: any): void;
registerDependencies(dependencies: string[]): void;
restore(): void;
/** @param {String|Function} generator */
run(generator: any): IRunContext;
}
export interface IRunContext {
async(): Function;
inDir(dirPath: string): IRunContext;
/** @param {String|String[]} args */
withArguments(args: any): IRunContext;
withGenerators(dependencies: string[]): IRunContext;
withOptions(options: any): IRunContext;
withPrompts(answers: any): IRunContext;
}
/** @type file file-utils */
var file: any;
var assert: IAssert;
var test: ITestHelper;
// "generators" is deprecated
namespace generators {
export class NamedBase extends YeomanGeneratorBase implements INamedBase {
constructor(args: string | string[], options: any);
}
export class Base extends NamedBase implements IBase {
static extend(protoProps: IQueueProps, staticProps?: any): IYeomanGenerator;
}
}
export class NamedBase extends YeomanGeneratorBase implements INamedBase {
constructor(args: string | string[], options: any);
}
export class Base extends NamedBase implements IBase {
static extend(protoProps: IQueueProps, staticProps?: any): IYeomanGenerator;
}
}
declare module "yeoman-generator" {
export = yo;
}