Yeoman generator add a new file generated exsiting project - javascript

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,
}
);
}
};

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.

Adding manual approval stage in CDK CodePipelines

I have been working with AWS CDK and I think its a great way to work with AWS. Recently I have got a problem which I am unable to resolve. Went over documentations and resources but none had explained how to do it in CDK. So I have two code pipelines and each pipeline either deploys to staging or production. Now I want a manual approval stage before the code gets deployed to production. I'll show my simple code below for reference:
import * as cdk from '#aws-cdk/core';
import { AppsPluginsCdkStack } from './apps-plugins-services/stack';
import {
CodePipeline,
ShellStep,
CodePipelineSource
} from '#aws-cdk/pipelines';
class ApplicationStage extends cdk.Stage {
constructor(scope: cdk.Construct, id: string) {
super(scope, id);
new CdkStack(this, 'cdkStack');
}
}
class ProductionPipelineStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props: cdk.StackProps) {
super(scope, id, props);
//Create the CDK Production Pipeline
const prodPipeline = new CodePipeline(this, 'ProductionPipeline', {
pipelineName: 'ProdPipeline',
synth: new ShellStep('ProdSynth', {
// Use a connection created using the AWS console to authenticate to GitHub
input: CodePipelineSource.connection(
'fahigm/cdk-repo',
'develop',
{
connectionArn:
'AWS-CONNECTION-ARN' // Created using the AWS console
}
),
commands: ['npm ci', 'npm run build', 'npx cdk synth']
})
});
prodPipeline.addStage(new ApplicationStage(this, 'Production'));
}
}
class StagingPipelineStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props: cdk.StackProps) {
super(scope, id, props);
//Create the CDK Staging Pipeline
const stagePipeline = new CodePipeline(this, 'StagingPipeline', {
pipelineName: 'StagePipeline',
synth: new ShellStep('StageSynth', {
// Use a connection created using the AWS console to authenticate to GitHub
input: CodePipelineSource.connection(
'fahigm/cdk-repo',
'master',
{
connectionArn:
'AWS-CONNECTION-ARN' // Created using the AWS console
}
),
commands: ['npm ci', 'npm run build', 'npx cdk synth']
})
});
stagePipeline.addStage(new ApplicationStage(this, 'Staging'));
}
}
//
const app = new cdk.App();
new ProductionPipelineStack(app, 'ProductionCDKPipeline', {
env: { account: 'ACCOUNT', region: 'REGION' }
});
new StagingPipelineStack(app, 'StagingCDKPipeline', {
env: { account: 'ACCOUNT', region: 'REGION' }
});
app.synth();
Now I don't know where to go from here. The documentation only talks about how to do it from console but I want to add it in the code. Would really appreciate any help!
The CDK documentation doesn't actually talk about how to do it from console, it talks about how to do it with CDK, and provides examples. Here's an example straight from the docs:
The following example shows both an automated approval in the form of a ShellStep, and a manual approval in the form of a ManualApprovalStep added to the pipeline. Both must pass in order to promote from the PreProd to the Prod environment:
declare const pipeline: pipelines.CodePipeline;
const preprod = new MyApplicationStage(this, 'PreProd');
const prod = new MyApplicationStage(this, 'Prod');
pipeline.addStage(preprod, {
post: [
new pipelines.ShellStep('Validate Endpoint', {
commands: ['curl -Ssf https://my.webservice.com/'],
}),
],
});
pipeline.addStage(prod, {
pre: [
new pipelines.ManualApprovalStep('PromoteToProd'),
],
});
Here's the documentation about the manual approval step specifically: https://docs.aws.amazon.com/cdk/api/latest/docs/#aws-cdk_pipelines.ManualApprovalStep.html

#sentry/node integration to wrap bunyan log calls as breadcrumbs

Sentry by defaults has integration for console.log to make it part of breadcrumbs:
Link: Import name: Sentry.Integrations.Console
How can we make it to work for bunyan logger as well, like:
const koa = require('koa');
const app = new koa();
const bunyan = require('bunyan');
const log = bunyan.createLogger({
name: 'app',
..... other settings go here ....
});
const Sentry = require('#sentry/node');
Sentry.init({
dsn: MY_DSN_HERE,
integrations: integrations => {
// should anything be handled here & how?
return [...integrations];
},
release: 'xxxx-xx-xx'
});
app.on('error', (err) => {
Sentry.captureException(err);
});
// I am trying all to be part of sentry breadcrumbs
// but only console.log('foo'); is working
console.log('foo');
log.info('bar');
log.warn('baz');
log.debug('any');
log.error('many');
throw new Error('help!');
P.S. I have already tried bunyan-sentry-stream but no success with #sentry/node, it just pushes entries instead of treating them as breadcrumbs.
Bunyan supports custom streams, and those streams are just function calls. See https://github.com/trentm/node-bunyan#streams
Below is an example custom stream that simply writes to the console. It would be straight forward to use this example to instead write to the Sentry module, likely calling Sentry.addBreadcrumb({}) or similar function.
Please note though that the variable record in my example below is a JSON string, so you would likely want to parse it to get the log level, message, and other data out of it for submission to Sentry.
{
level: 'debug',
stream:
(function () {
return {
write: function(record) {
console.log('Hello: ' + record);
}
}
})()
}

How to get Electron + rxdb to work?

I want to learn and develop a desktop app by using electron + rxdb.
My file structure:
main.js (the main process of electron)
/js-server/db.js (all about rxdb database, include creation)
/js-client/ui.js (renderer process of electron)
index.html (html home page)
main.js code:
const electron = require('electron')
const dbjs = require('./js-server/db.js')
const {ipcMain} = require('electron')
ipcMain.on('search-person', (event, userInput) => {
event.returnValue = dbjs.searchPerson(userInput);
})
db.js code:
var rxdb = require('rxdb');
var rxjs = require('rxjs');
rxdb.plugin(require('pouchdb-adapter-idb'));
const personSchema = {
title: 'person schema',
description: 'describes a single person',
version: 0,
type: 'object',
properties: {
Name: {type: 'string',primary: true},
Age: {type: 'string'},
},
required: ['Age']
};
var pdb;
rxdb.create({
name: 'persondb',
password: '123456789',
adapter: 'idb',
multiInstance: false
}).then(function(db) {
pdb = db;
return pdb.collection({name: 'persons', schema: personSchema})
});
function searchPerson(userInput) {
pdb.persons.findOne().where('Name').eq(userInput)
.exec().then(function(doc){return doc.Age});
}
module.exports = {
searchPerson: searchPerson
}
ui.js code:
const {ipcRenderer} = require('electron');
function getFormValue() {
let userInput = document.getElementById('searchbox').value;
displayResults(ipcRenderer.sendSync("search-person",userInput));
document.getElementById('searchbox').value = "";
}
Whenever I run this app, I got these errors:
(node:6084) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Error: RxError:
RxDatabase.create(): Adapter not added. (I am sure I've installed the pouched-adapter-idb module successfully)
Type error, cannot read property "persons" of undefined. (this error pops out when I search and hit enter to the form in index.html)
I am new to programming, especially js, I've been stuck on these errors for a week, just can't get it to work. Any help? Thanks.
The problem is that this line is in main.js:
const dbjs = require('./js-server/db.js')
Why? Because you're requiring RxDB inside the main process and using the IndexedDB adapter. IndexedDB is a browser API and thus can only be used in a rendering process. In Electron, the main process is a pure Node/Electron environment with no access to the Chromium API's.
Option #1
If you want to keep your database in a separate thread then consider spawning a new hidden browser window:
import {BrowserWindow} from 'electron'
const dbWindow = new BrowserWindow({..., show: false})
And then use IPC to communicate between the two windows similarly to how you have already done.
Option #2
Use a levelDB adapter that only requires NodeJS API's so you can keep your database in the main process.

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)

Categories