diff --git a/backend/.nycrc.json b/backend/.nycrc.json index d698f9e7a..290e52927 100644 --- a/backend/.nycrc.json +++ b/backend/.nycrc.json @@ -7,8 +7,8 @@ "temp-dir": "./reports/.nyc_output", "report-dir": "./reports/coverage", "check-coverage": true, - "branches": 22, - "lines": 41, - "functions": 30, - "statements": 41 + "branches": 29, + "lines": 44, + "functions": 33, + "statements": 44 } diff --git a/backend/package.json b/backend/package.json index 1b0da4055..d446072ae 100644 --- a/backend/package.json +++ b/backend/package.json @@ -5,7 +5,7 @@ "license": "Apache 2.0", "description": "Provide rich user experience for Yeoman generators using VSCode extension or the browser", "repository": "https://github.com/SAP/yeoman-ui", - "version": "0.0.12", + "version": "0.0.13", "engines": { "vscode": "^1.38.0" }, @@ -52,7 +52,7 @@ "lodash": "^4.17.15", "strip-ansi": "^6.0.0", "ws": "^7.2.0", - "yeoman-environment": "^2.6.0", + "yeoman-environment": "^2.7.0", "fs-extra": "^8.1.0" }, "devDependencies": { diff --git a/backend/src/yeomanui.ts b/backend/src/yeomanui.ts index 5a3cd0a28..32c6fdcc9 100644 --- a/backend/src/yeomanui.ts +++ b/backend/src/yeomanui.ts @@ -39,6 +39,9 @@ export class YeomanUI { private static defaultMessage = "Some quick example text of the generator description. This is a long text so that the example will look good."; private static YEOMAN_PNG = "yeoman.png"; + private static isWin32 = (process.platform === 'win32'); + private static CWD = path.join(os.homedir(), 'projects'); + private static NODE_MODULES = 'node_modules'; private rpc: IRpc; private logger: YouiLog; @@ -74,20 +77,36 @@ export class YeomanUI { // on the other hand, we never look for newly installed generators... const promise: Promise = new Promise(resolve => { - const env: Environment.Options = Environment.createEnv(); + const env: Environment.Options = this.getEnv(); env.lookup(async () => this.onEnvLookup(env, resolve, this.genFilter)); }); return promise; } - public async runGenerator(generatorName: string) { + private getEnv(): Environment.Options { + const env: Environment.Options = Environment.createEnv(); + const envGetNpmPaths: () => any = env.getNpmPaths; + env.getNpmPaths = function (localOnly:boolean = false) { + // Start with the local paths derived by cwd in vscode + // (as opposed to cwd of the plugin host process which is what is used by yeoman/environment) + // Walk up the CWD and add `node_modules/` folder lookup on each level + const parts: string[] = YeomanUI.CWD.split(path.sep); + const localPaths = _.map(parts, (part, index) => { + const resrpath = path.join(...parts.slice(0, index + 1), YeomanUI.NODE_MODULES); + return YeomanUI.isWin32 ? resrpath : path.join(path.sep, resrpath); - // TODO: ensure projects folder exist - const destinationRoot: string = path.join(os.homedir(), "projects"); + }); + const defaultPaths = envGetNpmPaths.call(this, localOnly); + + return _.uniq(localPaths.concat(defaultPaths)); + }; + return env; + } + public async runGenerator(generatorName: string) { // TODO: wait for dir to be created - fs.mkdir(destinationRoot, { recursive: true }, (err) => { + fs.mkdir(YeomanUI.CWD, { recursive: true }, (err) => { if (err) { console.error(err); } @@ -128,7 +147,7 @@ export class YeomanUI { this.promptCount = 0; this.gen = (gen as Generator); - this.gen.destinationRoot(destinationRoot); + this.gen.destinationRoot(YeomanUI.CWD); /* Generator.run() returns promise. Sending a callback is deprecated: https://yeoman.github.io/generator/Generator.html#run ... but .d.ts hasn't been updated for a while: @@ -142,7 +161,7 @@ export class YeomanUI { } console.log("done running yeomanui"); - message = `${generatorName} is done. Destination directory is ${destinationRoot}`; + message = `The '${generatorName}' project has been generated. You can find it at ${YeomanUI.CWD}`; this.doGeneratorDone(true, message); }); } catch (err) { @@ -212,7 +231,7 @@ export class YeomanUI { message: "", choices: _.compact(generatorChoices) }; - resolve({ name: "Generator Selection", questions: [generatorQuestion] }); + resolve({ name: "Select Generator", questions: [generatorQuestion] }); } private async getGeneratorChoice(genName: string, filter?: GeneratorFilter): Promise { diff --git a/backend/tests/yeomanui.spec.ts b/backend/tests/yeomanui.spec.ts index be98768dc..376aa6752 100644 --- a/backend/tests/yeomanui.spec.ts +++ b/backend/tests/yeomanui.spec.ts @@ -84,6 +84,10 @@ describe('yeomanui unit test', () => { } } + const rpc = new TestRpc(); + const logger = new TestLog(); + const yeomanUi: YeomanUI = new YeomanUI(rpc, logger); + before(() => { sandbox = sinon.createSandbox(); }); @@ -105,9 +109,6 @@ describe('yeomanui unit test', () => { }); describe("getGenerators", () => { - const rpc = new TestRpc(); - const logger = new TestLog(); - const yeomanUi: YeomanUI = new YeomanUI(rpc, logger); let envMock: any; const environment = { @@ -139,7 +140,7 @@ describe('yeomanui unit test', () => { message: "", choices: [] }; - expect(result).to.be.deep.equal({ name: "Generator Selection", questions: [generatorQuestion] }); + expect(result).to.be.deep.equal({ name: "Select Generator", questions: [generatorQuestion] }); }); it("get generators with type project", async () => { @@ -340,4 +341,72 @@ describe('yeomanui unit test', () => { expect(test3Choice.name).to.be.equal("test4"); }); }); + + describe("getEnv", () => { + const yeomanUi: YeomanUI = new YeomanUI(rpc, logger); + const testEnv = yeomanUi["getEnv"](); + const nodemodules = YeomanUI["NODE_MODULES"]; + testEnv.getNpmPaths = (localOnly: boolean = false): string[] => { + return localOnly ? + [path.join("localPath1", nodemodules), path.join("localPath2", nodemodules)] : + [path.join("path1", nodemodules), path.join("path2", nodemodules), path.join("localPath1", nodemodules), path.join("localPath2", nodemodules)]; + }; + + beforeEach(() => { + YeomanUI["CWD"] = path.join("root/project/folder"); + yeomanEnvMock.expects("createEnv").returns(testEnv); + }); + + it("env.getNpmPaths - localOnly is false, isWin32 is true", () => { + YeomanUI["isWin32"] = true; + const env = yeomanUi["getEnv"](); + const res = env.getNpmPaths(); + expect(res).to.have.lengthOf(7); + expect(res).to.include(path.join("root", nodemodules)); + expect(res).to.include(path.join("root", "project", nodemodules)); + expect(res).to.include(path.join("root", "project", "folder", nodemodules)); + expect(res).to.include(path.join("path1", nodemodules)); + expect(res).to.include(path.join("path2", nodemodules)); + expect(res).to.include(path.join("localPath1", nodemodules)); + expect(res).to.include(path.join("localPath2", nodemodules)); + }); + + it("env.getNpmPaths - localOnly is true, isWin32 is true", () => { + YeomanUI["isWin32"] = true; + const env = yeomanUi["getEnv"](); + const res = env.getNpmPaths(true); + expect(res).to.have.lengthOf(5); + expect(res).to.include(path.join("root", nodemodules)); + expect(res).to.include(path.join("root", "project", nodemodules)); + expect(res).to.include(path.join("root", "project", "folder", nodemodules)); + expect(res).to.include(path.join("localPath1", nodemodules)); + expect(res).to.include(path.join("localPath2", nodemodules)); + }); + + it("env.getNpmPaths - localOnly is false, isWin32 is false", () => { + YeomanUI["isWin32"] = false; + const env = yeomanUi["getEnv"](); + const res = env.getNpmPaths(); + expect(res).to.have.lengthOf(7); + expect(res).to.include(path.join(path.sep, "root", nodemodules)); + expect(res).to.include(path.join(path.sep, "root", "project", nodemodules)); + expect(res).to.include(path.join(path.sep, "root", "project", "folder", nodemodules)); + expect(res).to.include(path.join("path1", nodemodules)); + expect(res).to.include(path.join("path2", nodemodules)); + expect(res).to.include(path.join("localPath1", nodemodules)); + expect(res).to.include(path.join("localPath2", nodemodules)); + }); + + it("env.getNpmPaths - localOnly is true, isWin32 is false", () => { + YeomanUI["isWin32"] = false; + const env = yeomanUi["getEnv"](); + const res = env.getNpmPaths(true); + expect(res).to.have.lengthOf(5); + expect(res).to.include(path.join(path.sep, "root", nodemodules)); + expect(res).to.include(path.join(path.sep, "root", "project", nodemodules)); + expect(res).to.include(path.join(path.sep, "root", "project", "folder", nodemodules)); + expect(res).to.include(path.join("localPath1", nodemodules)); + expect(res).to.include(path.join("localPath2", nodemodules)); + }); + }); }); diff --git a/frontend/src/App.vue b/frontend/src/App.vue index e1d8d487d..3194bfbe2 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -189,7 +189,7 @@ export default { delete currentPrompt.status } } else { - // first prompt (Generator Selection) + // first prompt (Select Generator) prompt.active = true this.prompts.push(prompt) } @@ -253,7 +253,7 @@ export default { }, generatorDone(success, message) { if (this.currentPrompt.status === "pending") { - this.currentPrompt.name = "Done" + this.currentPrompt.name = "Confirmation" } this.doneMessage = message this.isDone = true diff --git a/frontend/src/components/Header.vue b/frontend/src/components/Header.vue index 0c49563d3..97471035a 100644 --- a/frontend/src/components/Header.vue +++ b/frontend/src/components/Header.vue @@ -1,11 +1,7 @@