How to create a custom blueprint? - jhipster

I'm trying to create a customized JHipster blueprint for my organization.
I've started my journey:
Installed Yeoman v4.3.0
Installed Jhipster v7.9.3
Created a directory for my future blueprint mkdir mygenerator && cd mygenerator
Executed the command to create a new blueprint: jhipster generate-blueprint
selected only the sub-generator server
add a cli: Y
Is server generator a side-by-side blueprint: Y
Is server generator a cli command: N
selected the tasks: initializing, prompting and configuring
From this point, I've opened the generated blueprint project with VS Code and noticed a first problem, some jhipster packages can't be resolved:
Unable to resolve path to module 'generator-jhipster/esm/generators/server'
Unable to resolve path to module 'generator-jhipster/esm/priorities'
I also noticed that the generator created for me has a small difference from the existing generators in the JHipster Github, such as jhipster-dotnetcore, generator-jhipster-quarkus, generator-jhipster-nodejs: the returned functions are async while in the cited repos they are regular functions (sync):
get [INITIALIZING_PRIORITY]() {
return {
async initializingTemplateTask() {},
};
}
Does it make any difference in this Jhipster version or there is no problem if I return the same way as jhipster-dotnetcore:
get initializing() {
return {
...super._initializing(),
setupServerConsts() {
this.packagejs = packagejs;
...
I've assumed that this detail is not important and followed with async function and write my prompting function to get some input from the user/developer in order to replace values in the template files :
get [PROMPTING_PRIORITY]() {
return {
...super._prompting(),
async promptingTemplateTask() {
const choices = [
{
name: 'OAuth 2.0 Protocol',
value: 'oauth2',
},
{
name: 'CAS Protocol',
value: 'cas',
},
];
const PROMPTS = {
type: 'list',
name: 'authenticationProtocol',
message: 'Which authentication protocol do you want to use?',
choices,
default: 'oauth2',
};
const done = this.async();
if (choices.length > 0) {
this.prompt(PROMPTS).then(prompt => {
this.authenticationProtocol = this.jhipsterConfig.authenticationProtocol = prompt.authenticationProtocol;
done();
});
} else {
done();
}
},
};
}
<%_ if (authenticationProtocol == 'oauth2') { _%>
security:
enable-csrf: true
oauth2:
client:
clientId: ${this.baseName}
clientSecret: Z3ByZXBmdGVy
accessTokenUri: http://localhost:8443/oauth2.0/accessToken
userAuthorizationUri: http://localhost:8443/oauth2.0/authorize
tokenName: oauth_token
authenticationScheme: query
clientAuthenticationScheme: form
logoutUri: http://localhost:8443/logout
clientSuccessUri: http://localhost:4200/#/login-success
resource:
userInfoUri: http://localhost:8443/oauth2.0/profile
<%_ } _%>
thymeleaf:
mode: HTML
templates/src/test/java/resources/config/application.yml.ejs
All this done, I've followed the next steps:
Run npm link inside the blueprint directory.
Created a new directory for a app example: mkdir appmygenerator && cd appmygenerator
Started a new example app with my blueprint: jhipster --blueprint mygenerator --skip-git --skip-install --skip-user-management --skip-client answering all question.
Here I've got some surprises:
After answering What is the base name of your application? I've got this warning: [DEP0148] DeprecationWarning: Use of deprecated folder mapping "./lib/util/" in the "exports" field module resolution of the package at /...<my-generator-path>/node_modules/yeoman-environment/package.json. Update this package.json to use a subpath pattern like "./lib/util/*"
My prompting function somehow made some questions be repeated, from question Do you want to make it reactive with Spring WebFlux? until Which other technologies would you like to use?.
When my prompt was finally shown, there was a message in front of the last option: CAS Protocol Run-async wrapped function (sync) returned a promise but async() callback must be executed to resolve
I've made some changes to my prompt function: removed the calling of super._prompting() with the hope to solve the item 2, and removed the async in the hope to solve the item 3.
Well ... apparently it was solved. But I get a new error when JHipster (or Yeoman) try process the template:
An error occured while running jhipster:server#writeFiles
ERROR! /home/fabianorodrigo/Downloads/my-blueprint/generators/server/templates/src/test/resources/config/application.yml.ejs:47
45| favicon:
46| enabled: false
>> 47| <%_ if (authenticationProtocol == 'oauth2') { _%>
48| security:
49| enable-csrf: true
50| oauth2:
authenticationProtocol is not defined
How come authenticationProtocol is not defined? I'm stuck here. What I could noticed is that, in all the Jhipster's generators I've cited above, the prompt function sets the properties like "this.[property] = [value]" and the "this.jhipsterConfig.[property] = [value]" and in the templates they are referenced (just the property's name) and it works.
What am I missing? Why even if I set the property "this.authenticationProtocol" in the function prompting it is not seem at the template?

Yeoman (yo/yeoman-generator/yeoman-environment) are not required and should no be a dependency to avoid duplication in the dependency tree, unless you know what you are doing. JHipster customizes them, yeoman-test is required by tests.
Unable to resolve path to module is a bug at eslint-plugin-import
I also noticed that the generator created for me has a small difference from the existing generators in the JHipster Github, such as jhipster-dotnetcore, generator-jhipster-quarkus, generator-jhipster-nodejs. Those blueprints are quite old (blueprint support is changing very fast for v8/esm) and are full server/backend replacements, seems you are trying to add cas support. The use case is quite different.
Does it make any difference in this Jhipster version or there is no problem if I return the same way as jhipster-dotnetcore? Yes, get [INITIALIZING_PRIORITY]() is the new notation, and INITIALIZING_PRIORITY may be >initializing instead of initializing. The explanation is here. JHipster v8 will not support the old notation.
...super._prompting(), is used to ask original prompts, since this is a side-by-side blueprint, prompts will be duplicated.
[DEP0148] DeprecationWarning: Use of deprecated folder mapping "./lib/util/" is a bug in yeoman-environment, and should be fixed in next version.
CAS Protocol Run-async wrapped function (sync) returned a promise but async() callback must be executed to resolve is shown because you are using async function with const done = this.async(); done(); together.
this.async() is a to support async through callbacks before Promises were a js default.
There are a few blueprints that uses new notation and can be used as inspiration: native, ionic, jooq and entity-audit.
I didn't see anything about the writing priority, so it looks like you are overriding an existing template and the original generator will write it. For this reason you should inject you configuration into the original generator.
The end result should be something like:
get [INITIALIZING_PRIORITY]() {
return {
async initializingTemplateTask() {
this.info('this blueprint adds support to cas authentication protocol');
},
};
}
get [PROMPTING_PRIORITY]() {
return {
async promptingTemplateTask() {
await this.prompt({
type: 'list',
name: 'authenticationProtocol',
message: 'Which authentication protocol do you want to use?',
choices: [
{
name: 'OAuth 2.0 Protocol',
value: 'oauth2',
},
{
name: 'CAS Protocol',
value: 'cas',
},
],
default: 'oauth2',
}, this.blueprintStorage); // <- `this.blueprintStorage` tells the prompt function to store the configuration inside `.yo-rc.json` at the blueprint namespace.
},
};
}
get [CONFIGURING_PRIORITY]() {
return {
configuringTemplateTask() {
// Store the default configuration
this.blueprintConfig.authenticationProtocol = this.blueprintConfig.authenticationProtocol || 'oauth2';
},
};
}
get [LOADING_PRIORITY]() {
return {
loadingTemplateTask() {
// Load the stored configuration, the prompt can be skipped so this needs to be in another priority.
this.authenticationProtocol = this.blueprintConfig.authenticationProtocol;
// Inject the configuration into the original generator. If you are writing the template by yourself, this may be not necessary.
this.options.jhipsterContext.authenticationProtocol = this.blueprintConfig.authenticationProtocol;
},
};
}

Related

React UnhandledSchemeError - "node:buffer" is not handled by plugins

I'm trying to use a package in my React project that will allow me to make API calls (axios, node-fetch, got etc.)
When these packages are not installed, the app runs properly. When any of them are installed and called in the code, I'm facing the error as follows:
Ignoring the warnings, I believe the problem has its roots from the output below:
Failed to compile.
Module build failed: UnhandledSchemeError: Reading from "node:buffer" is not handled by plugins (Unhandled scheme).
Webpack supports "data:" and "file:" URIs by default.
You may need an additional plugin to handle "node:" URIs.
I tried everything. Reinstalled node_modules. Created a clean test app, tried there. Also did my research, didn't find any relevant, clear solution on this. Nothing helped.
What am I doing wrong??
DomException file content:
/*! node-domexception. MIT License. Jimmy Wärting <https://jimmy.warting.se/opensource> */
if (!globalThis.DOMException) {
try {
const { MessageChannel } = require('worker_threads'),
port = new MessageChannel().port1,
ab = new ArrayBuffer()
port.postMessage(ab, [ab, ab])
} catch (err) {
err.constructor.name === 'DOMException' && (
globalThis.DOMException = err.constructor
)
}
}
module.exports = globalThis.DOMException
npm version: 8.5.5
node version: 16.15.0
You can work around this with this Webpack configuration:
plugins: [
new webpack.NormalModuleReplacementPlugin(/node:/, (resource) => {
resource.request = resource.request.replace(/^node:/, "");
}),
]

How can I add ESLInt custom rules directory to VSCode

I am running VSCode with the ESLint plugin. My project contains some custom rules, which live in a particular directory.
If I run ESLint from the command line and pass in --rulesdir=./custom-eslint-rules everything works as expected.
However, I am specifically referring to the linting that happens per file in the editor itself. There, it lints using the normal rules, but shows errors that the definitions for my custom rules are missing.
How can I configure VSCode so that the per-file linting sees my custom rule definitions that live in a particular directory?
In your setting.json:
"eslint.options": {
"rulePaths": "<path>"
}
Reference: https://eslint.org/docs/developer-guide/nodejs-api#cliengine
Write your own eslint plugin and use it with vs code compatibility 🚀
‌
1. install eslint-plugin-rulesdir#0.2.1
yarn add --dev eslint-plugin-rulesdir#0.2.1
2. add to .eslintrc.js
import
const rulesDirPlugin = require("eslint-plugin-rulesdir");
rulesDirPlugin.RULES_DIR = "src/rules"; // it is example
plugins
plugins: [...,"rulesdir"]
rules
rules: {
...,
"rulesdir/no-get-payments": "error"
}
3. create rule file src/rules/no-get-payments.js
module.exports = {
create: function (context) {
return {
CallExpression: function (node) {
if (node.callee.name === "getPayments") {
context.report({
node: node,
message: "getPayments is depricated! ",
});
}
},
};
},
};
‌

Configure ESLint RuleTester to use Typescript Parser

I am trying to write some custom ESLint rules for my typescript based project. In my project I am using eslint/typescript for linting.
I have already written a custom eslint plugin which validates a custom rule. Now I want to write the unit tests for that custom rule. My test file looks like this:
/**
* #fileoverview This rule verifies that logic can only depend on other logic
* #author Dayem Siddiqui
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const typescriptParser = require('#typescript-eslint/parser')
var rule = require("../../../lib/rules/logic-dependency"),
RuleTester = require("eslint").RuleTester;
//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
typescriptParser.parseForESLint()
var ruleTester = new RuleTester({ parserOptions: {} });
ruleTester.run("logic-dependency", rule, {
valid: [
`class DeleteLogic {
}
class CreateLogic {
constructor(private deleteLogic: DeleteLogic) {}
}`
],
invalid: [
{
code: `class ExternalClient {
}
class UpdateLogic {
constructor(private externalClient: ExternalClient) {}
}`,
errors: [
{
message: "Logic cannot have a dependency on client",
type: "MethodDefinition"
}
]
}
]
});
Right now my tests a failing because by default eslint only understand plain Javascript code. I understand that I need to somehow configure it to use a custom parser that allows it to understand/parse typescript code. However I am unable to find a good example online on how to do that
The RuleTester constructor takes eslint's parser options that allow for configuring the parser itself. So pass it in like this and Bob's your uncle:
const ruleTester = new RuleTester({
parser: '#typescript-eslint/parser',
});
typescript-eslint's own rule tests (e.g. this one) use exactly this.
(Was searching for an answer to this question and kept up ending here, so I posted the solution I found here in the hope it'll be useful.)
eslint 6.0+
Incorporating #Sonata's comment:
Eslint 6.0+ requires an absolute path to the parser (see 6.0 migration guide):
const ruleTester = new RuleTester({
parser: require.resolve('#typescript-eslint/parser'),
});
Use a package such as the typescript-eslint/parser. I can't provide much more than a link in this case. If you need help using it, let me know.

Cannot delete files from firebase collection

I am following the example listed here, except with modifications due to the API of the new firebase-tools.
exports.clearMessages = functions.runWith({ timeoutSeconds: 540, memory: '2GB' }).https.onCall(messagesController.clearMessages)
export const clearMessages = async (data, context) => {
const uid = context.auth.uid
const path = `users/${uid}/messages`
return firebase_tools.firestore.delete('flightApp3', path, {
recursive: true,
shallow: true,
allCollections: true
}).then(result => {
console.log('delete result', result)
return result
})
}
However, when I run this , I see the following displayed in Cloud Functions log:
Unhandled error { Error
at Error.FirebaseError (/user_code/node_modules/firebase-tools/lib/error.js:9:18)
at module.exports (/user_code/node_modules/firebase-tools/lib/getProjectId.js:10:19)
at Command.module.exports (/user_code/node_modules/firebase-tools/lib/requirePermissions.js:11:21)
at /user_code/node_modules/firebase-tools/lib/command.js:154:38
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
name: 'FirebaseError',
message: 'No project active. Run with \u001b[1m--project <projectId>\u001b[22m or define an alias by\nrunning \u001b[1mfirebase use --add\u001b[22m',
children: [],
status: 500,
exit: 1,
stack: 'Error\n at Error.FirebaseError (/user_code/node_modules/firebase-tools/lib/error.js:9:18)\n at module.exports (/user_code/node_modules/firebase-tools/lib/getProjectId.js:10:19)\n at Command.module.exports (/user_code/node_modules/firebase-tools/lib/requirePermissions.js:11:21)\n at /user_code/node_modules/firebase-tools/lib/command.js:154:38\n at process._tickDomainCallback (internal/process/next_tick.js:135:7)',
original: undefined,
context: undefined }
However, I'm pretty sure I have an active project in my firebase CLI.
$ firebase use
Active Project: production (flightApp3)
Project aliases for /Users/myUser/Developer/flightApp3/cloud:
* default (flightApp3)
* production (flightApp3)
Run firebase use --add to define a new project alias.
some options cannot be mixed ...
return firebase_tools.firestore.delete('flightApp3', path, {
// allCollections: true,
recursive: true,
yes: true
}).then(() => {
return {
path: path
};
});
that's how the path is being built up (path and allCollections also do not seem to make sense together): projects/${project}/databases/(default)/documents/users/${uid}/messages
getProjectId.js checks for rc.projects (where options.project is option --project):
module.exports = function(options, allowNull) {
if (!options.project && !allowNull) {
var aliases = _.get(options, "rc.projects", {});
...
these rc.projects are the projects from the .firebaserc file:
{
"projects": {
"default": "flightApp3"
}
}
or run firebase use default to switch from alias production to default (or remove alias production once for a test). FirestoreDelete(project, path, options) also does not care about options.token nor options.project anymore (as the documentation suggests).
$ firebase firestore:delete --help explains the command-line options:
Usage: firestore:delete [options] [path]
Delete data from Cloud Firestore.
Options:
-r, --recursive Recursive. Delete all documents and sub-collections.
Any action which would result in the deletion of child
documents will fail if this argument is not passed.
May not be passed along with --shallow.
--shallow Shallow. Delete only parent documents and ignore documents
in sub-collections. Any action which would orphan documents
will fail if this argument is not passed.
May not be passed along with --recursive.
--all-collections Delete all. Deletes the entire Firestore database,
including all collections and documents.
Any other flags or arguments will be ignored.
-y, --yes No confirmation. Otherwise, a confirmation prompt will appear.
the npm package (the output above) is at version 6.0.1.
just found a relevant comment (but possibly obsolete):
The token must be set in the functions config, and can be generated at the command line by running firebase login:ci.
this hints for environment configuration, so that functions.config().fb.token has the token:
firebase functions:config:set fb.token="THE TOKEN"
one can also obtain the projectId from process.env.FIREBASE_CONFIG.projectId.
Docs https://firebase.google.com/docs/firestore/solutions/delete-collections
In /functions directory install firebase-tools
"firebase-tools": "^7.16.2"
In cloud function import firebase-tools and call delete
const firebaseTools = require("firebase-tools");
...
firebaseTools.firestore.delete(workspaceRef.path, {
project: process.env.GCLOUD_PROJECT, // required
recursive: true, // required
yes: true // required
})
There is no need for a token when calling firebase-tools from a cloud function.
Also the link to the API https://github.com/firebase/firebase-tools/blob/v7.16.2/src/firestore/delete.js with code implementation for FirestoreDelete seems wrong.
I'm successfully calling .delete(path, options) but the code says .delete(project, path, options)?

require('use-strict') doesn't work for me

here, I am attempting to set value for read-only property but I am not getting any error:
HERE IS MY CODE:
require('use-strict');
function Employee(firstname) {
var _firstname = firstname;
Object.defineProperty(this, 'firstName', {
get: function () { return _firstname },
//set: function (value) { _firstname = value }
});
}
var employee = new Employee('Fawad');
employee.firstName = 'Yasir'; //Attempting to set a value for read-only property.
console.log(employee.firstName);
From the documentation for the use-strict package:
The implementation works by patching Node's internal module.wrapper
array, and then freezing it, so that further modifications are not
possible.
Also, this means that the current module will not be affected. You
should still "use strict" in the module that does
require('use-strict'). This module applies strictness to all future
modules loaded by your program.
Use of "use strict"; at the top of page worked for me although this approach is usually used for JavaScript development. I was trying to use one of the node.js packages which didn't worked.

Resources