Skip to content

Commit 1830c20

Browse files
Beginning Yeoman generator generator. Currently creates templates in as patches vs a common set of files.
1 parent 5534f1b commit 1830c20

16 files changed

Lines changed: 22301 additions & 0 deletions

File tree

templates/yeoman/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/node_modules/
2+
/built/
3+
/generator-aspnet-spa/

templates/yeoman/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
To generator the Yeoman generator, execute:
2+
3+
npm run build
4+
5+
Output will appear in the `generator-aspnet-spa` directory.

templates/yeoman/package.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "generator-aspnetspa-generator",
3+
"version": "1.0.0",
4+
"description": "Creates the Yeoman generator for ASP.NET Core SPA templates",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1",
8+
"build": "tsc && node ./built/CreateGenerator.js"
9+
},
10+
"author": "Microsoft",
11+
"license": "Apache-2.0",
12+
"dependencies": {
13+
"diff": "^2.2.2",
14+
"gitignore-parser": "0.0.2",
15+
"glob": "^7.0.3",
16+
"lodash": "^4.11.1",
17+
"mkdirp": "^0.5.1",
18+
"rimraf": "^2.5.2"
19+
}
20+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import * as yeoman from 'yeoman-generator';
2+
import * as glob from 'glob';
3+
import * as gitignore from 'gitignore-parser';
4+
import * as fs from 'fs';
5+
import * as path from 'path';
6+
import * as _ from 'lodash';
7+
import * as diff from 'diff';
8+
import * as mkdirp from 'mkdirp';
9+
import * as rimraf from 'rimraf';
10+
11+
const textFileExtensions = ['.gitignore', '.config', '.cs', '.cshtml', 'Dockerfile', '.html', '.js', '.json', '.jsx', '.md', '.ts', '.tsx'];
12+
13+
const templates = {
14+
'angular-2': '../../templates/Angular2Spa/',
15+
'knockout': '../../templates/KnockoutSpa/',
16+
'react-redux': '../../templates/ReactReduxSpa/',
17+
'react': '../../templates/ReactSpa/'
18+
};
19+
20+
function isTextFile(filename: string): boolean {
21+
return textFileExtensions.indexOf(path.extname(filename).toLowerCase()) >= 0;
22+
}
23+
24+
function writeFileEnsuringDirExists(root: string, filename: string, contents: string | Buffer) {
25+
let fullPath = path.join(root, filename);
26+
mkdirp.sync(path.dirname(fullPath));
27+
fs.writeFileSync(fullPath, contents);
28+
}
29+
30+
function listFilesExcludingGitignored(root: string): string[] {
31+
let gitignoreEvaluator = gitignore.compile(fs.readFileSync(path.join(root, '.gitignore'), 'utf8'));
32+
return glob.sync('**/*', { cwd: root, dot: true, nodir: true })
33+
.filter(fn => gitignoreEvaluator.accepts(fn));
34+
}
35+
36+
function writeCommonFiles(outDir: string) {
37+
let filesByTemplate = _.mapValues(templates, listFilesExcludingGitignored);
38+
let commonFiles = _.intersection.apply(_, _.values(filesByTemplate));
39+
40+
commonFiles.forEach(fn => {
41+
let templateRoots = _.values(templates);
42+
let origContent = fs.readFileSync(path.join(templateRoots[0], fn));
43+
44+
if (isTextFile(fn)) {
45+
// For text files, we copy the portion that's common to all the templates
46+
let commonText = origContent.toString('utf8');
47+
templateRoots.slice(1).forEach(otherTemplateRoot => {
48+
let otherTemplateContent = fs.readFileSync(path.join(otherTemplateRoot, fn), 'utf8');
49+
commonText = diff.diffLines(commonText, otherTemplateContent)
50+
.filter(c => !(c.added || c.removed))
51+
.map(c => c.value)
52+
.join('');
53+
});
54+
55+
writeFileEnsuringDirExists(outDir, fn, commonText);
56+
} else {
57+
// For binary (or maybe-binary) files, we only consider them common if they are identical across all templates
58+
let isIdenticalEverywhere = !templateRoots.slice(1).some(otherTemplateRoot => {
59+
return !fs.readFileSync(path.join(otherTemplateRoot, fn)).equals(origContent);
60+
});
61+
if (isIdenticalEverywhere) {
62+
writeFileEnsuringDirExists(outDir, fn, origContent);
63+
}
64+
}
65+
});
66+
}
67+
68+
function writeDiffsForTemplate(sourceRoot: string, destRoot: string, commonRoot: string) {
69+
listFilesExcludingGitignored(sourceRoot).forEach(fn => {
70+
const commonFn = path.join(commonRoot, fn);
71+
const sourceContent = fs.readFileSync(path.join(sourceRoot, fn));
72+
73+
if (!fs.existsSync(commonFn)) {
74+
// This file is unique to this template - just copy as-is
75+
writeFileEnsuringDirExists(destRoot, fn, sourceContent);
76+
} else {
77+
let commonText = fs.readFileSync(commonFn, 'utf8');
78+
let sourceText = sourceContent.toString('utf8');
79+
if (commonText !== sourceText) {
80+
// Write a diff vs the common version of this file
81+
let fileDiff = diff.createPatch(fn, commonText, sourceText, null, null);
82+
writeFileEnsuringDirExists(destRoot, fn + '.patch', fileDiff);
83+
}
84+
}
85+
});
86+
}
87+
88+
const outputRoot = './generator-aspnet-spa';
89+
const commonRoot = path.join(outputRoot, 'templates/common');
90+
rimraf.sync(outputRoot);
91+
writeCommonFiles(commonRoot);
92+
93+
_.forEach(templates, (templateRootDir, templateName) => {
94+
writeDiffsForTemplate(templateRootDir, path.join(outputRoot, 'templates', templateName), commonRoot);
95+
});

templates/yeoman/tsconfig.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"compilerOptions": {
3+
"moduleResolution": "node",
4+
"target": "es5",
5+
"outDir": "built",
6+
"sourceMap": false
7+
},
8+
"exclude": [
9+
"node_modules",
10+
"generator-aspnet-spa"
11+
]
12+
}

templates/yeoman/tsd.json

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"version": "v4",
3+
"repo": "borisyankov/DefinitelyTyped",
4+
"ref": "master",
5+
"path": "typings",
6+
"bundle": "typings/tsd.d.ts",
7+
"installed": {
8+
"yeoman-generator/yeoman-generator.d.ts": {
9+
"commit": "544a35a10866b32afda9c7f029c0764558563f4f"
10+
},
11+
"node/node.d.ts": {
12+
"commit": "544a35a10866b32afda9c7f029c0764558563f4f"
13+
},
14+
"glob/glob.d.ts": {
15+
"commit": "544a35a10866b32afda9c7f029c0764558563f4f"
16+
},
17+
"minimatch/minimatch.d.ts": {
18+
"commit": "544a35a10866b32afda9c7f029c0764558563f4f"
19+
},
20+
"lodash/lodash.d.ts": {
21+
"commit": "544a35a10866b32afda9c7f029c0764558563f4f"
22+
},
23+
"diff/diff.d.ts": {
24+
"commit": "544a35a10866b32afda9c7f029c0764558563f4f"
25+
},
26+
"mkdirp/mkdirp.d.ts": {
27+
"commit": "544a35a10866b32afda9c7f029c0764558563f4f"
28+
},
29+
"rimraf/rimraf.d.ts": {
30+
"commit": "544a35a10866b32afda9c7f029c0764558563f4f"
31+
}
32+
}
33+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Type definitions for diff
2+
// Project: https://github.com/kpdecker/jsdiff
3+
// Definitions by: vvakame <https://github.com/vvakame/>
4+
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
5+
6+
declare namespace JsDiff {
7+
interface IDiffResult {
8+
value: string;
9+
count?: number;
10+
added?: boolean;
11+
removed?: boolean;
12+
}
13+
14+
interface IBestPath {
15+
newPos: number;
16+
componenets: IDiffResult[];
17+
}
18+
19+
class Diff {
20+
ignoreWhitespace:boolean;
21+
22+
constructor(ignoreWhitespace?:boolean);
23+
24+
diff(oldString:string, newString:string):IDiffResult[];
25+
26+
pushComponent(components:IDiffResult[], value:string, added:boolean, removed:boolean):void;
27+
28+
extractCommon(basePath:IBestPath, newString:string, oldString:string, diagonalPath:number):number;
29+
30+
equals(left:string, right:string):boolean;
31+
32+
join(left:string, right:string):string;
33+
34+
tokenize(value:string):any; // return types are string or string[]
35+
}
36+
37+
function diffChars(oldStr:string, newStr:string):IDiffResult[];
38+
39+
function diffWords(oldStr:string, newStr:string):IDiffResult[];
40+
41+
function diffWordsWithSpace(oldStr:string, newStr:string):IDiffResult[];
42+
43+
function diffJson(oldObj: Object, newObj: Object): IDiffResult[];
44+
45+
function diffLines(oldStr:string, newStr:string):IDiffResult[];
46+
47+
function diffCss(oldStr:string, newStr:string):IDiffResult[];
48+
49+
function createPatch(fileName:string, oldStr:string, newStr:string, oldHeader:string, newHeader:string):string;
50+
51+
function applyPatch(oldStr:string, uniDiff:string):string;
52+
53+
function convertChangesToXML(changes:IDiffResult[]):string;
54+
55+
function convertChangesToDMP(changes:IDiffResult[]):{0: number; 1:string;}[];
56+
}
57+
58+
declare module "diff" {
59+
export = JsDiff;
60+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
declare module 'gitignore-parser' {
2+
export function compile(gitignoreContents: string): any;
3+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Type definitions for Glob 5.0.10
2+
// Project: https://github.com/isaacs/node-glob
3+
// Definitions by: vvakame <https://github.com/vvakame/>
4+
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
5+
6+
/// <reference path="../node/node.d.ts" />
7+
/// <reference path="../minimatch/minimatch.d.ts" />
8+
9+
declare module "glob" {
10+
11+
import events = require("events");
12+
import fs = require('fs');
13+
import minimatch = require("minimatch");
14+
15+
function G(pattern: string, cb: (err: Error, matches: string[]) => void): void;
16+
function G(pattern: string, options: G.IOptions, cb: (err: Error, matches: string[]) => void): void;
17+
18+
namespace G {
19+
function sync(pattern: string, options?: IOptions): string[];
20+
21+
function hasMagic(pattern: string, options?: IOptions): boolean;
22+
23+
var Glob: IGlobStatic;
24+
var GlobSync: IGlobSyncStatic;
25+
26+
interface IOptions extends minimatch.IOptions {
27+
cwd?: string;
28+
root?: string;
29+
dot?: boolean;
30+
nomount?: boolean;
31+
mark?: boolean;
32+
nosort?: boolean;
33+
stat?: boolean;
34+
silent?: boolean;
35+
strict?: boolean;
36+
cache?: { [path: string]: any /* boolean | string | string[] */ };
37+
statCache?: { [path: string]: fs.Stats };
38+
symlinks?: any;
39+
sync?: boolean;
40+
nounique?: boolean;
41+
nonull?: boolean;
42+
debug?: boolean;
43+
nobrace?: boolean;
44+
noglobstar?: boolean;
45+
noext?: boolean;
46+
nocase?: boolean;
47+
matchBase?: any;
48+
nodir?: boolean;
49+
ignore?: any; /* string | string[] */
50+
follow?: boolean;
51+
realpath?: boolean;
52+
nonegate?: boolean;
53+
nocomment?: boolean;
54+
55+
/** Deprecated. */
56+
globDebug?: boolean;
57+
}
58+
59+
interface IGlobStatic extends events.EventEmitter {
60+
new (pattern: string, cb?: (err: Error, matches: string[]) => void): IGlob;
61+
new (pattern: string, options: IOptions, cb?: (err: Error, matches: string[]) => void): IGlob;
62+
prototype: IGlob;
63+
}
64+
65+
interface IGlobSyncStatic {
66+
new (pattern: string, options?: IOptions): IGlobBase
67+
prototype: IGlobBase;
68+
}
69+
70+
interface IGlobBase {
71+
minimatch: minimatch.IMinimatch;
72+
options: IOptions;
73+
aborted: boolean;
74+
cache: { [path: string]: any /* boolean | string | string[] */ };
75+
statCache: { [path: string]: fs.Stats };
76+
symlinks: { [path: string]: boolean };
77+
realpathCache: { [path: string]: string };
78+
found: string[];
79+
}
80+
81+
interface IGlob extends IGlobBase, events.EventEmitter {
82+
pause(): void;
83+
resume(): void;
84+
abort(): void;
85+
86+
/** Deprecated. */
87+
EOF: any;
88+
/** Deprecated. */
89+
paused: boolean;
90+
/** Deprecated. */
91+
maxDepth: number;
92+
/** Deprecated. */
93+
maxLength: number;
94+
/** Deprecated. */
95+
changedCwd: boolean;
96+
/** Deprecated. */
97+
cwd: string;
98+
/** Deprecated. */
99+
root: string;
100+
/** Deprecated. */
101+
error: any;
102+
/** Deprecated. */
103+
matches: string[];
104+
/** Deprecated. */
105+
log(...args: any[]): void;
106+
/** Deprecated. */
107+
emitMatch(m: any): void;
108+
}
109+
}
110+
111+
export = G;
112+
}

0 commit comments

Comments
 (0)