Skip to content

Commit

Permalink
Fastify support
Browse files Browse the repository at this point in the history
  • Loading branch information
goldbergyoni committed Mar 28, 2024
1 parent 1554a10 commit 19f5c63
Show file tree
Hide file tree
Showing 44 changed files with 8,556 additions and 37,268 deletions.
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
20.1.0
20.12.0
11 changes: 11 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "Attach to Process",
"port": 9229,
"skipFiles": [
"<node_internals>/**",
"${workspaceFolder}/node_modules/**/*.js"
],
"restart": true
},
{
"type": "node",
"request": "launch",
Expand Down
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@
"**/node_modules/*.*"
],
"smartStep": true
}
},
"typescript.tsdk": "node_modules/typescript/lib"
}
20,950 changes: 4,658 additions & 16,292 deletions package-lock.json

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,15 @@
"@types/sinon": "^10.0.11",
"axios": "^0.27.2",
"execa": "^5.1.1",
"jest": "^27.5.1",
"jest": "^29.7.0",
"jest-watch-suspend": "^1.1.2",
"jest-watch-typeahead": "^1.1.0",
"jest-watch-typeahead": "^2.2.2",
"node-notifier": "^10.0.1",
"nodemon": "^2.0.15",
"prettier": "2.6.2",
"sinon": "^13.0.1",
"ts-jest": "^27.1.5",
"ts-jest": "^29.1.2",
"ts-node": "^10.7.0",
"typescript": "4.6.2"
"typescript": "5.2.2"
}
}
5 changes: 5 additions & 0 deletions src/code-generator/entry-points/cli-entry-point.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ export function startAppGenerator() {
"The name of the app which will get used for the root folder name, package.json. and others",
"default-app-name"
)
.option(
"-wf, --web-framework <type>",
"The web framework to use, currently 'express' or 'fastify'",
"fastify"
)
.option(
"-o, --orm <type>",
"The Type of ORM to use, currently 'sequelize' or 'prisma'",
Expand Down
2 changes: 1 addition & 1 deletion src/code-generator/entry-points/interactive-cli.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ const QuestionsWizard = () => {
const generationOptions = factorDefaultOptions({
installDependencies: true,
targetDirectory: process.cwd(),
baseFramework: questionsWizard.chosenFramework,
webFramework: questionsWizard.chosenFramework,
});
await generateService.generateApp(generationOptions);
setQuestionsWizard({
Expand Down
1 change: 1 addition & 0 deletions src/code-generator/entry-points/non-interactive-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export async function handleNonInteractiveCommand(options: any) {
installDependencies: options.installDependencies,
overrideIfExists: options.overrideIfExists,
ORM: options.orm,
webFramework: options.webFramework,
targetDirectory: options.targetDirectory || process.cwd(),
appName: options.appName,
});
Expand Down
23 changes: 4 additions & 19 deletions src/code-generator/generation-logic/features/choose-orm.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import * as fsExtra from "fs-extra";
import path from "path";
import { generationOptions } from "../generation-options";
import * as replacementUtilities from "replace-in-file";

// shorten string replacement
import {
getMicroservicePath,
replacePhraseInFile,
} from "../string-manipulation-helpers";

// This feature allows the user to choose which ORM (DB mapper) to use
export async function chooseORM(
Expand Down Expand Up @@ -58,19 +59,3 @@ async function adjustTheCodeToSequelizeORM(microservicePath: string) {
);
await replacePhraseInFile(packageJSONPath, '"db:generate-client"(.*?)\n', "");
}

async function replacePhraseInFile(
path: string,
whatToReplaceInRegex: string,
replacement: string
) {
await replacementUtilities.replaceInFile({
files: path,
from: new RegExp(whatToReplaceInRegex, "g"),
to: replacement,
});
}

function getMicroservicePath(rootPath: string) {
return path.join(rootPath, "services", "order-service");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import path from "path";
import * as fsExtra from "fs-extra";
import { generationOptions } from "../generation-options";
import {
getMicroservicePath,
replacePhraseInAllFiles,
replacePhraseInFile,
} from "../string-manipulation-helpers";

export async function chooseWebFramework(
generatedAppRoot: string,
options: generationOptions
) {
const microservicePath = getMicroservicePath(generatedAppRoot);
if (options.webFramework === "express") {
await adjustTheCodeToExpressFramework(microservicePath);
} else if (options.webFramework === "fastify") {
await adjustTheCodeToFastifyFramework(microservicePath);
}
}

async function adjustTheCodeToFastifyFramework(microservicePath: string) {
const expressFolder = path.join(microservicePath, "entry-points-express");
await fsExtra.rmdir(expressFolder, { recursive: true });
const fastifyFolderOldName = path.join(
microservicePath,
"entry-points-fastify"
);
const packageJSONPath = path.join(microservicePath, "package.json");
await replacePhraseInFile(
packageJSONPath,
'"(.*?)express(.*?)": "(.*)"(,?)\n',
""
);
const fastifyFolderNewName = path.join(microservicePath, "entry-points");
await fsExtra.move(fastifyFolderOldName, fastifyFolderNewName, {
overwrite: true,
});
await replacePhraseInAllFiles(
microservicePath,
"/entry-points-fastify/api/",
"/entry-points/api/"
);
}

async function adjustTheCodeToExpressFramework(microservicePath: string) {
const fastifyFolder = path.join(microservicePath, "entry-points-fastify");
await fsExtra.rmdir(fastifyFolder, { recursive: true });
const expressFolderOldName = path.join(
microservicePath,
"entry-points-express"
);
const packageJSONPath = path.join(microservicePath, "package.json");
await replacePhraseInFile(
packageJSONPath,
'"(.*?)fastify(.*?)": "(.*)"(,?)\n',
""
);
const expressFolderNewName = path.join(microservicePath, "entry-points");
await fsExtra.rename(expressFolderOldName, expressFolderNewName);
await replacePhraseInAllFiles(
microservicePath,
"/entry-points-fastify/api/",
"/entry-points/api/"
);
}
7 changes: 6 additions & 1 deletion src/code-generator/generation-logic/generate-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import execa from "execa";
import { generationOptions } from "./generation-options";
import { AppError } from "../error-handling";
import { chooseORM } from "./features/choose-orm";
import { chooseWebFramework } from "./features/choose-web-framework";
// This is where the code generation logic lives. In high-level, based on the provided option, it creates
// a folder, decides which code to generate, run the code through a templating engine and emit it to the target folder
export const generateApp = async (options: generationOptions) => {
Expand Down Expand Up @@ -67,6 +68,10 @@ async function adjustCodeBasedOnFeatures(
generatedAppRoot: string,
options: generationOptions
) {
console.debug(
`About to adjust the copied code based on the provided options`,
options
);
await chooseORM(generatedAppRoot, options);
// Other features will be added here
await chooseWebFramework(generatedAppRoot, options);
}
4 changes: 2 additions & 2 deletions src/code-generator/generation-logic/generation-options.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export type generationOptions = {
appName: string;
ORM: "sequelize" | "prisma";
baseFramework: string;
webFramework: "express" | "fastify";
DBType: string;
mainMicroserviceName: string;
emitBestPracticesHints: boolean;
Expand All @@ -15,7 +15,7 @@ export const factorDefaultOptions = (
): generationOptions => {
const defaults = {
appName: "default-app-name",
baseFramework: "express",
webFramework: "fastify",
DBType: "pg",
ORM: "sequelize",
mainMicroserviceName: "microservice-example-1",
Expand Down
30 changes: 30 additions & 0 deletions src/code-generator/generation-logic/string-manipulation-helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import path from "node:path";
import * as replacementUtilities from "replace-in-file";

export async function replacePhraseInFile(
path: string,
whatToReplaceInRegex: string,
replacement: string
) {
await replacementUtilities.replaceInFile({
files: path,
from: new RegExp(whatToReplaceInRegex, "g"),
to: replacement,
});
}

export async function replacePhraseInAllFiles(
pathToRoot: string,
whatToReplaceInRegex: string,
replacement: string
) {
await replacementUtilities.replaceInFile({
files: [`${pathToRoot}/**/*.*`],
from: new RegExp(whatToReplaceInRegex, "g"),
to: replacement,
});
}

export function getMicroservicePath(rootPath: string) {
return path.join(rootPath, "services", "order-service");
}
74 changes: 74 additions & 0 deletions src/code-generator/test/generator.non-interactive-cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,87 @@ let emptyFolderForATest: string;

beforeEach(async () => {
emptyFolderForATest = await testHelpers.createUniqueFolder();
console.log(emptyFolderForATest);
});

afterEach(async () => {
//await fsExtra.remove(emptyFolderForATest);
});

describe("Non-interactive CLI component tests", () => {
describe("Web framework flag", () => {
test("When framework type is express, then the created entry points folder has only express folder and dependencies", async () => {
// Arrange

// Act
await execa("ts-node", [
"./bin/cli.ts",
"immediate",
`--target-directory=${emptyFolderForATest}`,
`--app-name=test`,
`--web-framework=express`,
]);

// Assert
const rootPath = path.join(
emptyFolderForATest,
"test",
"services",
"order-service"
);
const isFastifyInPackageJSON = await testHelpers.doesFileContainPhrase(
path.join(rootPath, "package.json"),
"fastify"
);
const doesEntryPointFolderExists =
await testHelpers.doesFolderExistInPath(
path.join(rootPath, "entry-points")
);
expect({
isFastifyInPackageJSON,
doesEntryPointFolderExists,
}).toStrictEqual({
isFastifyInPackageJSON: false,
doesEntryPointFolderExists: true,
});
});

test("When framework type is fastify, then the created entry points folder has only fastify folder and dependencies", async () => {
// Arrange

// Act
await execa("ts-node", [
"./bin/cli.ts",
"immediate",
`--target-directory=${emptyFolderForATest}`,
`--app-name=test`,
`--web-framework=fastify`,
]);

// Assert
const rootPath = path.join(
emptyFolderForATest,
"test",
"services",
"order-service"
);
const isExpressInPackageJSON = await testHelpers.doesFileContainPhrase(
path.join(rootPath, "package.json"),
"express"
);
const doesEntryPointFolderExists =
await testHelpers.doesFolderExistInPath(
path.join(rootPath, "entry-points")
);
expect({
isFastifyInPackageJSON: isExpressInPackageJSON,
doesEntryPointFolderExists,
}).toStrictEqual({
isFastifyInPackageJSON: false,
doesEntryPointFolderExists: true,
});
});
});
describe("ORM type", () => {
test("When ORM type is Prisma, then the created DAL folder has prisma dependency and files", async () => {
// Arrange
Expand Down
2 changes: 1 addition & 1 deletion src/code-templates/.nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
20.1.0
20.12.0
11 changes: 11 additions & 0 deletions src/code-templates/libraries/error-handling/app-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export class AppError extends Error {
constructor(
public name: string,
public message: string,
public HTTPStatus: number = 500,
public isCatastrophic = false,
public cause?: unknown
) {
super(message);
}
}
Loading

0 comments on commit 19f5c63

Please sign in to comment.