How to check if directory exists using Yeoman? - javascript

I am asking the user for a directory name, and then if the directory exists I want to ask them if they want to archive it. However I am not sure what function I can use inside Yeoman to achieve this. Here is my code.
prompting: function () {
return this.prompt([{
type: 'input',
name: 'project_directory',
message: 'What is the project directory name?'
}, {
type: 'confirm',
name: 'archive',
message: 'That project already exists. Do you want to archive it?',
when: function (answers) {
var destDir = 'project/' + answers.project_directory.replace(/[^0-9a-zA-Z\-.]/g, "").toLowerCase();
var fso = new ActiveXObject("Scripting.FileSystemObject");
//Return true if the folder exists
return (fso.FolderExists(destDir));
}
}]).then(function (answers) {
}.bind(this));
}

Yeoman doesn't provide any built-in methods to verify if a file or a directory exists.
But Yeoman is just Node.js, it's just JavaScript.
So you actually want to ask how to detect if a directory exist with Node.

If you inherited from Generator, you have this.fs object that has exists() method.
module.exports = class extends Generator {
/* ... */
default() {
this.alreadyCopied = this.fs.exists(this.destination('myfile.txt'));
}
}

Actually, as mentioned by Simon Boudrias, Yeoman doesn't provide a built-in method for it, but you can do the following:
var Generator = require('yeoman-generator');
var fs = require('fs');
module.exports = class extends Generator{
checkIfFolderExists(){
fs.stat('YourDirectoryHere'), function(error, stats){
if(stats!=undefined && stats.isDirectory()){
// your directory already exists.
}else{
// create your directory.
}
}) ;
}
}

Related

JHipster Blueprint - Generate files with a specific path using templates | Get default java package name

EDIT : the former question was "JHipster Blueprint - How to get default Java package name ?"
I am developing a blueprint with JHipster that overrides the entity-server sub-generator. The desired behaviour is to replace all files in /src/main/java/defaultpackageName/domain/ from the project generated by the blueprint with my generated files. This is my code (files.js):
const entityServerFiles = {
noHibernate: [
//domain files
{
path: 'src/main/java/XXX/domain/',
templates: [
{
file: 'Entity.java',
renameTo: generator => `${generator.persistClass}.java`
}
]
}
]
};
function writeFiles() {
return {
write() {
this.writeFilesToDisk(entityServerFiles, this, false);
}
}
}
module.exports = {
writeFiles
};
For now it just creates a folder XXX in /src/main/java/ with my generated files in it.
What would I need to write in the XXX in path: 'src/main/java/XXX/domain/' in order to generate the files at the right place?
I did some digging on github on the generator-jhipster project and the prompt asking the user for the default java package name is in /generator-jhipster/generators/java/index.cjs/. This is the whole code https://github.com/jhipster/generator-jhipster/blob/main/generators/java/index.cjs
But I just took the important part:
const {
PACKAGE_NAME,
PACKAGE_NAME_DEFAULT_VALUE,
PRETTIER_JAVA_INDENT,
PRETTIER_JAVA_INDENT_DEFAULT_VALUE,
BUILD_TOOL,
BUILD_TOOL_DEFAULT_VALUE,
BUILD_TOOL_PROMPT_CHOICES,
} = require('./constants.cjs');
get prompting() {
return {
async showPrompts() {
if (this.shouldSkipPrompts()) return;
await this.prompt(
[
{
name: PACKAGE_NAME,
type: 'input',
validate: input => this.validatePackageName(input),
message: 'What is your default Java package name?',
default: () => this.sharedData.getConfigDefaultValue(PACKAGE_NAME, PACKAGE_NAME_DEFAULT_VALUE),
},
],
this.config
);
},
};
}
From what I understand, I just need to access the PACKAGE_NAME constant from my blueprint and it should work. Any ideas?
I just found the solution...
const entityServerFiles = {
noHibernate: [
//domain files
{
path: 'src/main/java/',
templates: [
{
file: 'package/domain/Entity.java',
renameTo: generator => `${generator.entityAbsoluteFolder}/domain/${generator.persistClass}.java`
}
]
}
]
};
function writeFiles() {
return {
write() {
this.writeFilesToDisk(entityServerFiles, this, false);
}
}
}
module.exports = {
writeFiles
};
The path property specifies the path inside the templates folder. Meanwhile, you can specify the path you want your files to be generated inside the project in the renameTo property.
So the answer to my question is ${generator.entityAbsoluteFolder} which had nothing to do with my original hypothesis, but this question can also be useful for writing templates in general.

Yeoman generator add a new file generated exsiting project

I've yeoman generator which generate a simple sproject successfully.
I want that after the project generation, in latter time that the use will have the ability to generate a new file deployment.yaml under the app folder, however it needs to read some data from the main generator
for example appName as the sub-generator needs to generate a new file
inside the generated application.
e.g. yo tdk
This command generates a new project
And when I run yo tdk:event (or something similar) it will generate a new file inside the project app folder
For illustration I've created this very simple generator
const Generator = require("yeoman-generator");
module.exports = class extends Generator {
prompting() {
this.props = {
appName: "my-app",
srvName: "my-service"
};
const prompts = [
{
name: "appName",
message: "Project name: ",
type: "input",
default: this.props.appName
},
{
name: "srvName",
message: "Service name: ",
type: "input",
default: this.props.srvName
}
];
return this.prompt(prompts).then(props => {
this.props = props;
});
}
writing() {
this.fs.copyTpl(
this.templatePath("app"),
this.destinationPath(this.props.appName),
this.props
);
}
};
This generator have two simple question
app name
service name
And it will generate a project like
myapp /root
-app /folder
- service.yaml /single file at the project generation
The generated service.yaml looks like following:
apiVersion: v1
kind: Service
metadata:
name: <%= appName %>
spec:
selector:
app: <%= srvName %>
ports:
- protocol: TCP
port: 80
Now after the generation of the project with this service.yaml file
I want in latter time (after the project generation)to add new file deployment.yaml under the app folder
deployment.yaml
apiVersion: v1
kind: Deployment
metadata:
name: <%= appName %> //this is the appname from the project generation
spec:
replicas: <%= replica %>
selector:
app: <%= srvName %>
The appName & srvName are coming from the main generator,
(I saw that there is option to share data between sub generator
https://yeoman.io/authoring/storage.html , not sure how to share this between generators )
and the replica should come from the new/sub generator
This is the project structure after the generation
myapp /root
-app /folder
- service.yaml /single file at the project generation
- deployment.yaml / new file added to the project under app folder
Like user start another generator/sub and have a new question e.g. how much replicas do you want? and then generates the file.
How can I do it ?
update
This is my project strucutre
myapp
- node_modules
- package.json //here I declare the main-generator command -> tdk
- generators
-- app
---index.ts
--deployment
---index.ts
---package.json //here I declare the sub-generator command -> deploy
- node_modules
- package.json
-.yo-rc.json //here I see the data that I keep via config.set api
Update
When I call to the sub generator via program like
const yeoman = require('yeoman-environment');
const env = yeoman.createEnv();
env.lookup(function () {
env.run("tdk:deploy", {
replicas: 100
}, (err) => {
console.log("done", err);
});
});
I got error:
out from config undefined : undefined //the undefind is from the console in the sub-generator
done TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received type undefined
at validateString (internal/validators.js:125:11)
at Object.join (path.js:1037:7)
I put a console.log in the subgenerator code like
initializing() {
this.srvName = this.config.get("srvName");
this.appName = this.config.get("appName");
console.log("out from config", this.srvName, ":", this.appName);
}
And when I run the subgenerator I got empty config ( from the .yo-rc.json)
while checking the .yo-rc.json . I was able to see the entry from the main generator, the data was stored but when I run it from the program it doesnt find it...any idea ?
This is the link for both project (very basic yeoman generator which demonstrate the point) just need to run npm install for both projects
and for the generator run also npm link.
At the end: a project should be generated with two files
1. service.yaml // generated from the main generator
2. deployment.yaml - // generated from sub generator with the properties from the main & sub generator
currently, the deployment.yaml file is not generated
https://drive.google.com/drive/folders/1kBnZxpVcRR9qhGZagVtod7W4wFmt73C6
1 . generator-tdk - Generator and sub-generator
2. yeomanEnv - The code which is running the sub-generator to create the file inside the generated project
What am I doing wrong ? :(
if there is a way from the sub-generator to read the .yo-rc.json , it can help
You can set the values to config inside configuring of the main generator like this:
configuring() {
this.config.set('appName', this.props.appName);
this.config.set('srvName', this.props.srvName);
}
and read the values inside the sub-generators:
initializing() {
this.srvName = this.config.get("srvName");
this.appName = this.config.get("appName");
}
So you'll have access to these values via this.srvName and this.appName upon writing.
Example code:
app/index.js:
const Generator = require("yeoman-generator");
module.exports = class extends Generator {
prompting() {
this.props = {
appName: "my-app",
srvName: "my-service",
};
const prompts = [
{
name: "appName",
message: "Project name: ",
type: "input",
default: this.props.appName,
},
{
name: "srvName",
message: "Service name: ",
type: "input",
default: this.props.srvName,
},
];
return this.prompt(prompts).then((props) => {
this.props = props;
});
}
configuring() {
this.config.set('appName', this.props.appName);
this.config.set('srvName', this.props.srvName);
}
writing() {
this.fs.copyTpl(
this.templatePath("app"),
this.destinationPath(this.props.appName),
this.props
);
}
};
deploy/index.js:
const Generator = require("yeoman-generator");
module.exports = class extends Generator {
initializing() {
this.srvName = this.config.get("srvName");
this.appName = this.config.get("appName");
}
prompting() {
this.props = {
replicas: 0,
};
const prompts = [
{
name: "replica",
message: "how much replicas do you want?",
type: "input",
default: this.props.replicas,
},
];
return this.prompt(prompts).then((props) => {
this.props = props;
});
}
writing() {
this.fs.copyTpl(
this.templatePath("deploy"),
this.destinationPath(this.appName),
{
srvName: this.srvName,
appName: this.appName,
...this.props,
}
);
}
};
and commands:
yo <name for the main project generation
yo <name>:deploy to ask for replicas and create deployment.yaml
To execute the sub-generator without the use of yo:
var yeoman = require("yeoman-environment");
var env = yeoman.createEnv();
env.lookup(function () {
env.run("<name>:deploy", {
replicas: 100
}, (err) => {
console.log("done", err);
});
});
and a sample sub-generator that skips question if values are passed via options (deploy/index.js):
const Generator = require("yeoman-generator");
module.exports = class extends Generator {
initializing() {
this.srvName = this.config.get("srvName");
this.appName = this.config.get("appName");
}
prompting() {
this.props = {
replicas: 0,
};
const prompts = [
{
name: "replicas",
message: "which app to generate?",
type: "input",
default: this.props.replicas,
when: !this.options.replicas, // disable the question if it's found in options
},
];
return this.prompt(prompts).then((props) => {
this.props = props;
// set values from options (if found)
this.props.replicas = this.options.replicas || this.props.replicas;
});
}
writing() {
this.fs.copyTpl(
this.templatePath("deploy"),
this.destinationPath(this.appName),
{
srvName: this.srvName,
appName: this.appName,
...this.props,
}
);
}
};

.find is not a function error

I'm developing a node js rest server and having an issue with my Schema queries. When I hit my end points I get the error TypeError: user.find is not a function
The following is my user.js file
var {mongoose} = require('../../dbcore/mongoose');
var Schema = mongoose.Schema;
module.exports = mongoose.model('User',new Schema( {
basicId: Schema.ObjectId,
activePurchaseIDs: {
type: [Schema.ObjectId],
default: []
},
activeOrderIDs: {
type: [Schema.ObjectId],
default: []
},
paymentOptionIDs: {
type: [Schema.ObjectId],
default: []
},
addressIDs: {
type: [Schema.ObjectId],
default: []
},
interestIDs: {
type: [Schema.ObjectId],
default: []
}
}));
and this is where it's imported/required.
var URLS = require('./urls');
var User = require('../schemas/user/user');
function init(app,mongoose) {
app.get(URLS.USERS_URL,(req,res)=>{
var user = new User({});
user.find().then((users)=>{
res.send({users});
},(err)=>{
res.status(400).send(err);
});
});
}
module.exports = init;
I was following a tutorial while writing this code and I was expecting it to work as I followed the tutorial step by step.
When you call var user = new User({}) you are creating a new MongoDB document based on the User model and assigning it to var user.
A single user document does not have a find() function, but your User model does.
var user = new User({});
User.find().then(...);
app.get(URLS.USERS_URL, async (req,res)=>{
const userList = await User.find();
if(!userList) {
res.status(500).json({success: false});
}
res.send(userList);
});
Your call to the database needs to look like this:
User.find().then((users)=>{
res.send({users});
}).catch((err)=>{
res.status(400).send(err);
});
You should call it directly on the module, because mongoose will handle creation implicitly and creating a new object isn't neccesary.
I'm not sure if your schema is correctly defined, but I'm not going to say your tutorial is wrong on that. You should go into mongo shell and check if the schema was created to verify it was designed correctly.
In my case, I wrote wrong this so check your file exports module.exports = XYZ format.
PS:- I wrote like this exports.module = XYZ

Yeoman task not copying directory

I'm trying to build a simple yeoman task that copies a template directory into the destination directory where the user is running the command. The prompt method is working but nothing is being written or copied. Any idea where I'm going wrong here?
'use strict';
//Require dependencies
var yeoman = require('yeoman-generator');
var chalk = require('chalk');
var yosay = require('yosay');
module.exports = class extends yeoman {
//Ask for user input
prompting() {
var done = this.async();
this.prompt({
type: 'input',
name: 'name',
message: 'Your project name',
//Defaults to the project's folder name if the input is skipped
default: this.appname
}, function(answers) {
this.props = answers
this.log(answers.name);
done();
}.bind(this));
}
//Writing Logic here
writing() {
this.fs.copyTpl(
this.templatePath('testfile'),
this.destinationPath('testfile')
);
}
};
The prompt method doesn't take a callback since release 1.0
Instead you want this.prompt([...]).then(callback)

Yeoman copy function doesn't work after prompting

i am fiddleing around with yeoman and want to write my first generator for a simple html5 boilerplate. My problem is, that the two functions in my generator work well on their own but not together and i don't know why. I checked some generators from the yeoman page, but i don't see what i am doing wrong. I hope you can help me. This is my code:
'use strict';
var generators = require('yeoman-generator');
var yosay = require('yosay');
module.exports = generators.Base.extend({
initializing: function(){
this.log(yosay("\'Allo \'allo I will create your HTML5 Boilerplate..."));
},
prompting: function() {
var done = this.async();
this.prompt({
type: 'input',
name: 'name',
message: 'Your project name',
//Defaults to the project's folder name if the input is skipped
default: this.appname
}, function(answers) {
this.props = answers
this.log(answers.name);
done();
}.bind(this));
},
writing: function(){
this.fs.copyTpl(
this.templatePath('_page/_index.html'),
this.destinationPath('index.html'),
{ title: "answers.name" }
);
},
});
Thanks in advance!
Try using the Promises version of the prompting function, as shown on yeoman.io.
Example:
prompting: function() {
return this.prompt({
type: 'input',
name: 'name',
message: 'Your project name',
//Defaults to the project's folder name if the input is skipped
default: this.appname
}).then(function(answers) {
this.props = answers
this.log(answers.name);
}.bind(this));
},
Changes:
add return before this.prompt().
change this.prompt(prompts, callback); to this.prompt(prompts).then(callback);

Categories