chore(repo): add custom nx generators (nest:lib, typeorm:tomg)

This commit is contained in:
Francesco Spilla 2025-02-07 10:35:01 +01:00
parent e985a27632
commit 711f55ab11
33 changed files with 10979 additions and 131 deletions

18
nx.json
View File

@ -1,10 +1,7 @@
{ {
"$schema": "./node_modules/nx/schemas/nx-schema.json", "$schema": "./node_modules/nx/schemas/nx-schema.json",
"namedInputs": { "namedInputs": {
"default": [ "default": ["{projectRoot}/**/*", "sharedGlobals"],
"{projectRoot}/**/*",
"sharedGlobals"
],
"production": [ "production": [
"default", "default",
"!{projectRoot}/.eslintrc.json", "!{projectRoot}/.eslintrc.json",
@ -39,9 +36,14 @@
"options": { "options": {
"targetName": "test" "targetName": "test"
}, },
"exclude": [ "exclude": ["apps/ebitemp-api-e2e/**/*"]
"apps/ebitemp-api-e2e/**/*" }
] ],
"targetDefaults": {
"@nx/js:tsc": {
"cache": true,
"dependsOn": ["^build"],
"inputs": ["production", "^production"]
}
} }
]
} }

View File

@ -2,13 +2,20 @@
"name": "@repo/source", "name": "@repo/source",
"version": "0.0.0", "version": "0.0.0",
"license": "MIT", "license": "MIT",
"scripts": {}, "scripts": {
"start": "nx run-many --target serve",
"build": "nx run-many --target build",
"lint": "nx run-many --target lint",
"postinstall": "patch-package"
},
"private": true, "private": true,
"dependencies": { "dependencies": {
"@nestjs/common": "^10.0.2", "@nestjs/common": "^10.0.2",
"@nestjs/core": "^10.0.2", "@nestjs/core": "^10.0.2",
"@nestjs/platform-express": "^10.0.2", "@nestjs/platform-express": "^10.0.2",
"@nx/devkit": "20.4.1",
"axios": "^1.6.0", "axios": "^1.6.0",
"patch-package": "^8.0.0",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rxjs": "^7.8.0" "rxjs": "^7.8.0"
}, },
@ -22,23 +29,28 @@
"@nx/js": "20.4.1", "@nx/js": "20.4.1",
"@nx/nest": "20.4.1", "@nx/nest": "20.4.1",
"@nx/node": "20.4.1", "@nx/node": "20.4.1",
"@nx/plugin": "20.4.1",
"@nx/web": "20.4.1", "@nx/web": "20.4.1",
"@nx/webpack": "20.4.1", "@nx/webpack": "20.4.1",
"@nx/workspace": "20.4.1", "@nx/workspace": "20.4.1",
"@swc-node/register": "~1.9.1", "@swc-node/register": "~1.9.1",
"@swc/cli": "~0.3.12",
"@swc/core": "~1.5.7", "@swc/core": "~1.5.7",
"@swc/helpers": "~0.5.11", "@swc/helpers": "~0.5.11",
"@types/jest": "^29.5.12", "@types/jest": "^29.5.12",
"@types/node": "~18.16.9", "@types/node": "~18.16.9",
"eslint": "^9.8.0", "eslint": "^9.8.0",
"eslint-config-prettier": "^9.0.0", "eslint-config-prettier": "^9.0.0",
"execa": "5.1.1",
"jest": "^29.7.0", "jest": "^29.7.0",
"jest-environment-node": "^29.7.0", "jest-environment-node": "^29.7.0",
"mssql": "^11.0.1",
"nx": "20.4.1", "nx": "20.4.1",
"prettier": "^2.6.2", "prettier": "^2.6.2",
"ts-jest": "^29.1.0", "ts-jest": "^29.1.0",
"ts-node": "10.9.1", "ts-node": "10.9.1",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"typeorm-model-generator": "0.4.6-no-engines",
"typescript": "~5.7.2", "typescript": "~5.7.2",
"typescript-eslint": "^8.19.0", "typescript-eslint": "^8.19.0",
"webpack-cli": "^5.1.4" "webpack-cli": "^5.1.4"

File diff suppressed because one or more lines are too long

1765
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

7
tools/nest/README.md Normal file
View File

@ -0,0 +1,7 @@
# nest
This library was generated with [Nx](https://nx.dev).
## Building
Run `nx build nest` to build the library.

View File

@ -0,0 +1,11 @@
{
"generators": {
"library": {
"factory": "./src/generators/library/library",
"schema": "./src/generators/library/schema.json",
"aliases": ["lib"],
"x-type": "library",
"description": "Create a NestJS Library for Nx."
}
}
}

13
tools/nest/package.json Normal file
View File

@ -0,0 +1,13 @@
{
"name": "@repo/nest",
"version": "0.0.1",
"private": true,
"type": "commonjs",
"main": "./src/index.js",
"types": "./src/index.d.ts",
"dependencies": {
"@nx/devkit": "20.4.1",
"tslib": "^2.3.0"
},
"generators": "./generators.json"
}

41
tools/nest/project.json Normal file
View File

@ -0,0 +1,41 @@
{
"name": "tools/nest",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "tools/nest/src",
"projectType": "library",
"tags": [],
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/tools/nest",
"main": "tools/nest/src/index.ts",
"tsConfig": "tools/nest/tsconfig.lib.json",
"assets": [
"tools/nest/*.md",
{
"input": "./tools/nest/src",
"glob": "**/!(*.ts)",
"output": "./src"
},
{
"input": "./tools/nest/src",
"glob": "**/*.d.ts",
"output": "./src"
},
{
"input": "./tools/nest",
"glob": "generators.json",
"output": "."
},
{
"input": "./tools/nest",
"glob": "executors.json",
"output": "."
}
]
}
}
}
}

View File

@ -0,0 +1,3 @@
export * from './lib/<%= fileName %>.module';<% if(controller) { %>
export * from './lib/<%= fileName %>.controller';<% } %><% if(service) { %>
export * from './lib/<%= fileName %>.service';<% } %>

View File

@ -0,0 +1,20 @@
import { Test } from '@nestjs/testing';
import { <%= className %>Controller } from './<%= fileName %>.controller';<% if(service) { %>
import { <%= className %>Service } from './<%= fileName %>.service';<% } %>
describe('<%= className %>Controller', () => {
let controller: <%= className %>Controller;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [<% if(service) { %><%= className %>Service<% } %>],
controllers: [<%= className %>Controller]
}).compile();
controller = module.get(<%= className %>Controller);
});
it('should be defined', () => {
expect(controller).toBeTruthy();
});
})

View File

@ -0,0 +1,7 @@
import { Controller } from '@nestjs/common';<% if(service) { %>
import { <%= className %>Service } from './<%= fileName %>.service';<% } %>
@Controller('<%= fileName %>')
export class <%= className %>Controller {<% if(service) { %>
constructor(private <%= propertyName %>Service: <%= className %>Service) {}
<% } %>}

View File

@ -0,0 +1,11 @@
import { Module<% if(global) { %>, Global<% } %> } from '@nestjs/common';<% if(controller) { %>
import { <%= className %>Controller } from './<%= fileName %>.controller';<% } %><% if(service) { %>
import { <%= className %>Service } from './<%= fileName %>.service';<% } %>
<% if(global) { %>
@Global()<% } %>
@Module({
controllers: [<% if(controller) { %><%= className %>Controller<% } %>],
providers: [<% if(service) { %><%= className %>Service<% } %>],
exports: [<% if(service) { %><%= className %>Service<% } %>],
})
export class <%= className %>Module {}

View File

@ -0,0 +1,18 @@
import { Test } from '@nestjs/testing';
import { <%= className %>Service } from './<%= fileName %>.service';
describe('<%= className %>Service', () => {
let service: <%= className %>Service;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [<%= className %>Service]
}).compile();
service = module.get(<%= className %>Service);
});
it('should be defined', () => {
expect(service).toBeTruthy();
});
})

View File

@ -0,0 +1,6 @@
import { Injectable, Logger } from '@nestjs/common';
@Injectable()
export class <%= className %>Service {
protected readonly logger = new Logger(<%= className %>Service.name);
}

View File

@ -0,0 +1,57 @@
import {
formatFiles,
generateFiles,
joinPathFragments,
names,
OverwriteStrategy,
readProjectConfiguration,
Tree,
} from '@nx/devkit';
import { LibGeneratorSchema } from './schema';
import { libraryGenerator } from '@nx/nest';
export async function libGenerator(tree: Tree, options: LibGeneratorSchema) {
const projectConfig = readProjectConfiguration(tree, options.project);
const typeDirectory = (() => {
switch (options.type) {
case 'module':
return 'modules';
case 'feature':
return 'features';
}
})();
const libraryName = `${projectConfig.name}-${options.name}`;
const libraryRoot = joinPathFragments(
projectConfig.root,
'src',
typeDirectory,
libraryName
);
const opts: Parameters<typeof libraryGenerator>[1] = {
directory: libraryRoot,
name: libraryName,
buildable: true,
linter: 'eslint',
setParserOptionsProject: true,
unitTestRunner: 'jest',
strict: true,
simpleName: false,
};
await libraryGenerator(tree, opts);
tree.delete(joinPathFragments(libraryRoot, 'src'));
generateFiles(
tree,
joinPathFragments(__dirname, './files/common'),
libraryRoot,
{ ...opts, ...names(options.name), controller: true, service: true },
{ overwriteStrategy: OverwriteStrategy.Overwrite }
);
await formatFiles(tree);
}
export default libGenerator;

View File

@ -0,0 +1,5 @@
export interface LibGeneratorSchema {
project: string;
name: string;
type: 'module'|'feature';
}

View File

@ -0,0 +1,50 @@
{
"$schema": "https://json-schema.org/schema",
"$id": "Library",
"title": "Create a NestJS Library for Nx",
"description": "Create a NestJS Library for Nx.",
"type": "object",
"properties": {
"project": {
"description": "The project in which to place the library.",
"type": "string",
"alias": "p",
"x-prompt": "Which project to scaffold?",
"x-dropdown": "projects"
},
"type": {
"description": "The library type that becomes directory where the library is placed.",
"type": "string",
"alias": "type",
"$default": {
"$source": "argv",
"index": 0
},
"x-prompt": {
"message": "Which type of library would you like to generate?",
"type": "list",
"items": [
{
"value": "feature",
"label": "Feature module (features/lib...)"
},
{
"value": "module",
"label": "Application module (modules/lib...)"
}
]
}
},
"name": {
"description": "Library name.",
"pattern": "(?:^@[a-zA-Z0-9-*~][a-zA-Z0-9-*._~]*\\/[a-zA-Z0-9-~][a-zA-Z0-9-._~]*|^[a-zA-Z][^:]*)$",
"type": "string",
"$default": {
"$source": "argv",
"index": 1
},
"x-prompt": "What name would you like to use?"
}
},
"required": ["type", "name"]
}

0
tools/nest/src/index.ts Normal file
View File

13
tools/nest/tsconfig.json Normal file
View File

@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs"
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

View File

@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"declaration": true,
"types": ["node"]
},
"include": ["src/**/*.ts"]
}

7
tools/typeorm/README.md Normal file
View File

@ -0,0 +1,7 @@
# typeorm
This library was generated with [Nx](https://nx.dev).
## Building
Run `nx build typeorm` to build the library.

View File

@ -0,0 +1,10 @@
{
"generators": {
"typeorm-model-generator": {
"factory": "./src/generators/typeorm-model-generator/typeorm-model-generator",
"schema": "./src/generators/typeorm-model-generator/schema.json",
"aliases": ["tomg"],
"description": "Creates typeorm entities via typeorm-model-generator."
}
}
}

View File

@ -0,0 +1,15 @@
{
"name": "@repo/typeorm",
"version": "0.0.1",
"private": true,
"type": "commonjs",
"main": "./src/index.js",
"types": "./src/index.d.ts",
"dependencies": {
"@nx/devkit": "20.4.1",
"execa": "5.1.1",
"typeorm-model-generator": "0.4.6-no-engines",
"tslib": "^2.3.0"
},
"generators": "./generators.json"
}

View File

@ -0,0 +1,41 @@
{
"name": "tools/typeorm",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "tools/typeorm/src",
"projectType": "library",
"tags": [],
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/tools/typeorm",
"main": "tools/typeorm/src/index.ts",
"tsConfig": "tools/typeorm/tsconfig.lib.json",
"assets": [
"tools/typeorm/*.md",
{
"input": "./tools/typeorm/src",
"glob": "**/!(*.ts)",
"output": "./src"
},
{
"input": "./tools/typeorm/src",
"glob": "**/*.d.ts",
"output": "./src"
},
{
"input": "./tools/typeorm",
"glob": "generators.json",
"output": "."
},
{
"input": "./tools/typeorm",
"glob": "executors.json",
"output": "."
}
]
}
}
}
}

View File

@ -0,0 +1,22 @@
[
{
"pluralizeNames": true,
"noConfigs": true,
"convertCaseFile": "none",
"convertCaseEntity": "pascal",
"convertCaseProperty": "camel",
"convertEol": "LF",
"propertyVisibility": "none",
"lazy": false,
"activeRecord": false,
"generateConstructor": true,
"customNamingStrategyPath": ".tomg-naming-strategy.js",
"relationIds": false,
"strictMode": "none",
"skipSchema": true,
"indexFile": false,
"exportType": "named",
"skipNonPrimaryKeyIndexes": true,
"removeColumnsInRelation": false
}
]

View File

@ -0,0 +1,39 @@
/**
* @typedef {import('typeorm-model-generator').Column} Column
* @typedef {import('typeorm-model-generator').Entity} Entity
*/
/**
* Customizes the entity name.
* @param {string} oldEntityName - The default entity name.
* @param {Entity} entity - The entity.
* @returns {string} The new entity name.
*/
function entityName(oldEntityName, entity) {
return oldEntityName + 'Entity';
}
/**
* Customizes the column name.
* @param {string} oldColumnName - The default column name.
* @param {Column} column - The column.
* @returns {string} The new column name.
*/
function columnName(oldColumnName, column) {
return oldColumnName;
}
/**
* Customizes the file name.
* @param {string} oldFileName - The default file name.
* @returns {string} The new file name.
*/
function fileName(oldFileName) {
return oldFileName.replace('Entity', '.entity');
}
module.exports = {
entityName,
columnName,
fileName
};

View File

@ -0,0 +1,10 @@
export interface TomgGeneratorSchema {
driver: string;
host: string;
username: string;
password: string;
database: string;
project: string;
directory: string;
quiet: boolean;
}

View File

@ -0,0 +1,68 @@
{
"$schema": "https://json-schema.org/schema",
"$id": "typeorm-model-generator",
"title": "Creates typeorm entities via typeorm-model-generator.",
"description": "Creates typeorm entities via typeorm-model-generator.",
"type": "object",
"examples": [
{
"command": "nx g tomg --host 192.168.0.0 --database database -u sa -x pa$$w0rd -e mssql -q",
"description": "Generate typeorm entities for the database identified by the specified connection, in quiet mode"
}
],
"properties": {
"project": {
"description": "The project in which to place the entities.",
"type": "string",
"alias": "p",
"x-prompt": "Which project to scaffold?",
"x-dropdown": "projects"
},
"directory": {
"description": "A directory where the entities are placed.",
"type": "string",
"alias": "dir",
"x-prompt": "Which directory do you want to create the entities in?",
"default": "typeorm-model-generator-output",
"x-priority": "important"
},
"driver": {
"description": "The database driver that should be used by the generator.",
"type": "string",
"alias": "e",
"x-prompt": "Database driver?",
"default": "mssql"
},
"database": {
"description": "The name of the database whose tables should be scaffolded.",
"type": "string",
"x-prompt": "Database name?"
},
"host": {
"description": "The host of the database whose tables should be scaffolded.",
"type": "string",
"x-prompt": "Database host?"
},
"username": {
"description": "The username of database whose tables should be scaffolded.",
"type": "string",
"alias": "u",
"x-prompt": "Database username?"
},
"password": {
"description": "The password of database whose tables should be scaffolded.",
"type": "string",
"alias": "x",
"x-prompt": "Database password?"
},
"quiet": {
"description": "Enables verbose logging.",
"type": "boolean",
"alias": "q",
"default": "true",
"x-priority": "internal",
"x-prompt": "Hide typeorm stdout/stderr?"
}
},
"required": []
}

View File

@ -0,0 +1,90 @@
import {
FileChange,
formatFiles,
generateFiles,
joinPathFragments,
OverwriteStrategy,
readProjectConfiguration,
Tree,
workspaceRoot,
} from '@nx/devkit';
import execa from 'execa';
import os from 'node:os';
import { mkdirSync, writeFileSync, chmodSync, rmSync } from 'node:fs';
import { mkdtemp, rm } from 'node:fs/promises';
import { dirname, join } from 'node:path';
import { TomgGeneratorSchema } from './schema';
export async function tomgGenerator(tree: Tree, options: TomgGeneratorSchema) {
const tmpDirPath = await mkdtemp(join(os.tmpdir(), 'tomg-'));
try {
const libraryRoot = readProjectConfiguration(tree, options.project).root;
generateFiles(
tree,
joinPathFragments(__dirname, './files'),
libraryRoot,
options,
{ overwriteStrategy: OverwriteStrategy.KeepExisting }
);
flushChanges(workspaceRoot, tree.listChanges());
const { stdout, stderr } = await execa(
'npx',
[
'typeorm-model-generator',
'-o',
tmpDirPath,
'-e',
options.driver,
'-h',
options.host,
'-d',
options.database,
'-u',
options.username,
'-x',
options.password,
],
{ cwd: libraryRoot }
);
if (!options.quiet) {
console.log(stdout);
console.error(stderr);
}
generateFiles(
tree,
tmpDirPath,
joinPathFragments(libraryRoot, options.directory),
options,
{ overwriteStrategy: OverwriteStrategy.Overwrite }
);
await formatFiles(tree);
console.log('Generation complete.');
} finally {
await rm(tmpDirPath, { recursive: true, force: true });
}
}
export default tomgGenerator;
function flushChanges(root: string, fileChanges: FileChange[]): void {
fileChanges.forEach((f) => {
const fpath = join(root, f.path);
if (f.type === 'CREATE') {
mkdirSync(dirname(fpath), { recursive: true });
writeFileSync(fpath, f.content!);
if (f.options?.mode) chmodSync(fpath, f.options.mode);
} else if (f.type === 'UPDATE') {
writeFileSync(fpath, f.content!);
if (f.options?.mode) chmodSync(fpath, f.options.mode);
} else if (f.type === 'DELETE') {
rmSync(fpath, { recursive: true, force: true });
}
});
}

View File

View File

@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs"
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

View File

@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"declaration": true,
"types": ["node"]
},
"include": ["src/**/*.ts"]
}

View File

@ -6,6 +6,7 @@
"declaration": false, "declaration": false,
"moduleResolution": "node", "moduleResolution": "node",
"emitDecoratorMetadata": true, "emitDecoratorMetadata": true,
"esModuleInterop": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"importHelpers": true, "importHelpers": true,
"target": "es2015", "target": "es2015",
@ -14,7 +15,10 @@
"skipLibCheck": true, "skipLibCheck": true,
"skipDefaultLibCheck": true, "skipDefaultLibCheck": true,
"baseUrl": ".", "baseUrl": ".",
"paths": {} "paths": {
"@repo/nest": ["tools/nest/src/index.ts"],
"@repo/typeorm": ["tools/typeorm/src/index.ts"]
}
}, },
"exclude": ["node_modules", "tmp"] "exclude": ["node_modules", "tmp"]
} }