Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/88 create cli tool for scaffolding #127

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ nbdist/
# Stack
minio/

# Additional
$(pwd)
# Node
node_modules

2 changes: 2 additions & 0 deletions refarch-cli/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Omits the resolved property (with specific reference to the npm repository) from the lockfile.
omit-lockfile-registry-resolved=true
3 changes: 3 additions & 0 deletions refarch-cli/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dist
target
node_modules
1 change: 1 addition & 0 deletions refarch-cli/.prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"@muenchen/prettier-codeformat"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix invalid .prettierrc.json format

The current format is invalid. When extending a shared Prettier configuration, it should be specified using proper JSON syntax.

Apply this diff to fix the configuration:

-"@muenchen/prettier-codeformat"
+{
+  "extends": "@muenchen/prettier-codeformat"
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"@muenchen/prettier-codeformat"
{
"extends": "@muenchen/prettier-codeformat"
}

34 changes: 34 additions & 0 deletions refarch-cli/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// @ts-check

import eslint from "@eslint/js";
import tseslint from "typescript-eslint";

export default tseslint.config(
{
ignores: [
"**/build/**",
"**/dist/**",
"index.js",
"eslint.config.js",
"rollup.config.js",
],
},
eslint.configs.recommended,
{
plugins: {
"@typescript-eslint": tseslint.plugin,
},
languageOptions: {
parser: tseslint.parser,
parserOptions: {
projectService: true,
},
Comment on lines +23 to +25
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix the TypeScript parser configuration

The projectService option is incorrect. To enable project-wide type checking, use project: true instead.

      parserOptions: {
-       projectService: true,
+       project: true,
      },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
parserOptions: {
projectService: true,
},
parserOptions: {
project: true,
},

},
rules: {
"no-console": "error",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error"],
},
files: ["index.ts"],
}
);
245 changes: 245 additions & 0 deletions refarch-cli/index.ts
simonhir marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
#!/usr/bin/env node
import { cpSync, renameSync, rmSync } from "fs";

import { confirm, input, select, Separator } from "@inquirer/prompts";
import { replaceInFileSync } from "replace-in-file";

enum applications {
FRONTEND = "frontend",
BACKEND = "backend",
EAI = "eai",
}
const EXIT = "exit";
let keepStack = true;
let keepDocs = true;
let hasJavaApplicationBeenGenerated = false;

/**
* Starting-point for the Projekt configuration.
* Depending on the value of the select, an interactive CLI will be shown
*/
async function projectConfiguration() {
await select({
message: "Select Project you want to generate with enter",
choices: [
{ name: applications.FRONTEND, value: applications.FRONTEND },
{ name: applications.BACKEND, value: applications.BACKEND },
{ name: applications.EAI, value: applications.EAI },
{ name: EXIT, value: EXIT },
new Separator(),
],
}).then(async (result: string) => {
switch (result) {
case applications.EAI:
await generateJavaInteractiveCli(applications.EAI);
break;
case applications.BACKEND:
await generateJavaInteractiveCli(applications.BACKEND);
break;
case applications.FRONTEND:
await generateFrontendInteractiveCli();
break;
}
if (result != EXIT) {
await projectConfiguration();
} else {
await generateDefaultCliForDocsAndStack();
Comment on lines +44 to +46
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid recursion in projectConfiguration to prevent potential stack overflow

The recursive call to projectConfiguration at line 44 may lead to a stack overflow if the user goes through many iterations. Consider refactoring the function to use a loop instead.

Apply this diff to refactor the code:

 async function projectConfiguration() {
+  let result: string;
+  do {
     result = await select({
       message: "Select the project you want to generate and press enter",
       choices: [
         { name: applications.FRONTEND, value: applications.FRONTEND },
         { name: applications.BACKEND, value: applications.BACKEND },
         { name: applications.EAI, value: applications.EAI },
         { name: EXIT, value: EXIT },
         new Separator(),
       ],
     });
     switch (result) {
       case applications.EAI:
         await generateJavaInteractiveCli(applications.EAI);
         break;
       case applications.BACKEND:
         await generateJavaInteractiveCli(applications.BACKEND);
         break;
       case applications.FRONTEND:
         await generateFrontendInteractiveCli();
         break;
     }
-    if (result != EXIT) {
-      await projectConfiguration();
-    } else {
-      await generateDefaultCliForDocsAndStack();
-    }
+  } while (result != EXIT);
+  await generateDefaultCliForDocsAndStack();
 }

Committable suggestion skipped: line range outside the PR's diff.

}
});
}

/**
* interactive CLI for the inputs, needed to generate the backend or eai
* @param application - java application that should be generated
*/
async function generateJavaInteractiveCli(application: string) {
hasJavaApplicationBeenGenerated = true;
const groupId = await input({
message:
"Define value for property groupId (should match expression '^de\\.muenchen\\.[a-z0-9]+(\\.[a-z0-9]+)*$'): ",
validate(value) {
const pass = value.match(/^de\.muenchen\.[a-z0-9]+(\.[a-z0-9]+)*$/g);
return pass ? true : "GroupId name not valid";
},
required: true,
});
const artifactId = await input({
message: "Define value for property artifactId:",
required: true,
DanielOber marked this conversation as resolved.
Show resolved Hide resolved
validate(value: string) {
const pass = value.match(/([a-z-])+/g);
return pass
? true
: "ArtifactId name not valid (should match expression '^de\\.muenchen\\.[a-z0-9]+(\\.[a-z0-9]+)*$'): ";
Comment on lines +70 to +73
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix inconsistency between validation and error message for artifactId

The validation regex for artifactId at line 70 is /([a-z-])+ but the error message refers to a different pattern. Ensure that the error message matches the regex used in validation.

Apply this diff to correct the error message:

       const pass = value.match(/([a-z-])+/g);
       return pass
         ? true
-        : "ArtifactId name not valid (should match expression '^de\\.muenchen\\.[a-z0-9]+(\\.[a-z0-9]+)*$'): ";
+        : "ArtifactId name not valid (should contain only lowercase letters and hyphens)";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const pass = value.match(/([a-z-])+/g);
return pass
? true
: "ArtifactId name not valid (should match expression '^de\\.muenchen\\.[a-z0-9]+(\\.[a-z0-9]+)*$'): ";
const pass = value.match(/([a-z-])+/g);
return pass
? true
: "ArtifactId name not valid (should contain only lowercase letters and hyphens)";

},
});
const packageName = await input({
message: "Define value for property package:",
default: groupId,
validate(value: string) {
const pass = value.match(/^de\.muenchen\.[a-z0-9]+(\.[a-z0-9]+)*$/g);
return pass ? true : "Package name not valid";
},
required: true,
});
if (application == applications.BACKEND) {
generateBackend(packageName, groupId, artifactId);
} else if (application == applications.EAI) {
generateEAI(packageName, groupId, artifactId);
}
}

/**
* Generates a new BACKEND package with the provided package name, groupId and artifactId.
* The function copies the existing `refarch-backend` folder to a new folder named `<artifactId>`.
* It then performs string replacements on the Java files and pom.xml file within the new folder
* to update the package name, groupId, and artifactId.
*
* @param packageName - The new package name to use for the Java files.
* @param groupId - The new groupId to use for the pom.xml file.
* @param artifactId - The new artifactId to use for the pom.xml file and the name of the new folder
*/
function generateBackend(
packageName: string,
groupId: string,
artifactId: string
) {
cpSync("refarch-backend", "refarch-backend-copy", {
recursive: true,
});
const replacements = [
{
files: "refarch-backend-copy/src/main/java/de/muenchen/refarch/**/*.java",
from: /de.muenchen.refarch/g,
to: `${packageName}`,
},
{
files: "refarch-backend-copy/pom.xml",
from: [
"<groupId>de.muenchen.refarch</groupId>",
"<artifactId>refarch-backend</artifactId>",
"<name>refarch_backend</name>",
],
to: [
`<groupId>${groupId}</groupId>`,
`<artifactId>${artifactId}</artifactId>`,
`<name>${artifactId}</name>`,
],
},
];
replacements.map((options) => replaceInFileSync(options));
renameSync("refarch-backend-copy", `${artifactId}`);
const packageNameWithSlashes = packageName.replace(/\./g, "/");

renameSync(
`${artifactId}/src/main/java/de/muenchen/refarch`,
`${artifactId}/src/main/java/${packageNameWithSlashes}`
);
}

async function generateFrontendInteractiveCli() {
const name = await input({
message: "Define value for property name:",
DanielOber marked this conversation as resolved.
Show resolved Hide resolved
required: true,
validate(value: string) {
const pass = value.match(/([a-z-])+/g);
return pass ? true : "Name not valid";
},
});
generateFrontend(name);
}

/**
* Generates a new FRONTEND directory with the provided name
* The function copies the existing `refarch-frontend` folder to a new folder named `<name>`.
* It then performs string replacements in the package.json and package-lock.json to update the name
*
* @param name - The new name to use for the application
*/
function generateFrontend(name: string) {
cpSync("refarch-frontend", "refarch-frontend-copy", {
recursive: true,
});
const replacements = {
files: [
"refarch-frontend-copy/package.json",
"refarch-frontend-copy/package-lock.json",
],
from: /refarch-frontend/g,
to: `${name}`,
};
replaceInFileSync(replacements);
renameSync("refarch-frontend-copy", `${name}`);
}

/**
* Generates a new EAI package with the provided package name, groupId and artifactId.
* The function copies the existing `refarch-eai` folder to a new folder named `<artifactId>`.
* It then performs string replacements on the Java files and pom.xml file within the new folder
* to update the package name, groupId, and artifactId.
*
* @param packageName - The new package name to use for the Java files.
* @param groupId - The new groupId to use for the pom.xml file.
* @param artifactId - The new artifactId to use for the pom.xml file and the name of the new folder
*/
function generateEAI(packageName: string, groupId: string, artifactId: string) {
cpSync("refarch-eai", "refarch-eai-copy", { recursive: true });
const replacements = [
{
files: "refarch-eai-copy/src/main/java/de/muenchen/refarch/**/*.java",
DanielOber marked this conversation as resolved.
Show resolved Hide resolved
from: /de.muenchen.refarch/g,
to: `${packageName}`,
},
{
files: "refarch-eai-copy/pom.xml",
from: [
"<groupId>de.muenchen.refarch</groupId>",
"<artifactId>refarch-eai</artifactId>",
"<name>refarch_eai</name>",
],
to: [
`<groupId>${groupId}</groupId>`,
`<artifactId>${artifactId}</artifactId>`,
`<name>${artifactId}</name>`,
],
},
];
replacements.map((options) => replaceInFileSync(options));
renameSync("refarch-eai-copy", `${artifactId}`);
const packageNameWithSlashes = packageName.replace(/\./g, "/");
renameSync(
`${artifactId}/src/main/java/de/muenchen/refarch`,
`${artifactId}/src/main/java/${packageNameWithSlashes}`
);
}

/**
* generated the interactive CLI for choosing, if the Docs and Stack folder should be kept
*/
async function generateDefaultCliForDocsAndStack() {
keepDocs = await confirm({ message: "Want to keep the Docs folder" });
keepStack = await confirm({ message: "Want to keep the Stack folder" });
cleanup();
}

/**
* remove the templates for the frontend, backend and eai.
* remove the shared files if no java-application got generated
* remove the docs and stack depending on the user input
*/
function cleanup() {
if (!keepDocs) {
rmSync("docs", { recursive: true });
simonhir marked this conversation as resolved.
Show resolved Hide resolved
}
if (!keepStack) {
rmSync("stack", { recursive: true });
}
if (!hasJavaApplicationBeenGenerated) {
rmSync("shared-files", { recursive: true });
}
rmSync("refarch-eai", { recursive: true });
rmSync("refarch-backend", { recursive: true });
rmSync("refarch-frontend", { recursive: true });
}

export default projectConfiguration();
Loading
Loading