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: rework cli to new hexancore style part 1 #11

Merged
merged 1 commit into from
Aug 2, 2024
Merged
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
893 changes: 0 additions & 893 deletions .yarn/releases/yarn-4.1.0.cjs

This file was deleted.

894 changes: 894 additions & 0 deletions .yarn/releases/yarn-4.3.1.cjs

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
nodeLinker: node-modules

yarnPath: .yarn/releases/yarn-4.1.0.cjs
npmPublishRegistry: "https://registry.npmjs.org"

npmRegistryServer: https://registry.npmjs.org
npmPublishRegistry: https://registry.npmjs.org
npmRegistryServer: "https://registry.npmjs.org"

yarnPath: .yarn/releases/yarn-4.3.1.cjs
2 changes: 1 addition & 1 deletion bin/hcli
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
#!/usr/bin/env node --optimize_for_size --max_old_space_size=460 --gc_interval=100
require("../lib/index.js");
require("../lib/main.js");
25 changes: 9 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@
"name": "@hexancore/cli",
"version": "0.1.3",
"engines": {
"node": ">=20"
"node": ">=22"
},
"engine-strict": true,
"description": "CLI of Hexancore framework",
"author": {
"name": "Andrzej Wasiak",
"url": "https://andrzejwasiak.pl"
},
"author": "Andrzej Wasiak",
"license": "MIT",
"main": "./lib/index.js",
"main": "./lib/main.js",
"repository": {
"type": "git",
"url": "git+https://github.com/hexancore/cli.git"
Expand All @@ -20,9 +17,9 @@
"access": "public"
},
"homepage": "https://github.com/hexancore/cli",
"packageManager": "yarn@4.1.0",
"packageManager": "yarn@4.3.1",
"scripts": {
"hcli": "node --optimize_for_size --max_old_space_size=460 --gc_interval=100 ./lib/index.js",
"hcli": "node --optimize_for_size --max_old_space_size=460 --gc_interval=100 ./lib/main.js",
"global": "npm i -g --force",
"build": "rm -fr ./lib && tsc -p tsconfig.build.json",
"lint": "eslint \"{src,test}/**/*.ts\"",
Expand All @@ -35,8 +32,7 @@
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --config .jestrc.json --runInBand"
},
"dependencies": {
"@commander-js/extra-typings": "^12.0.0",
"@hexancore/common": "^0.13.2",
"@hexancore/common": "^0.15.0",
"chalk": "4",
"commander": "^12.0.0",
"enquirer": "^2.4.1",
Expand All @@ -46,21 +42,18 @@
"inversify": "^6.0.2",
"reflect-metadata": "^0.1.13",
"ts-morph": "^21.0.1",
"tslib": "^2.6.2"
"tslib": "^2.6.3"
},
"devDependencies": {
"@commander-js/extra-typings": "^12.0.0",
"@hexancore/mocker": "^1.1.2",
"@swc/core": "^1.3.101",
"@swc/jest": "^0.2.29",
"@types/fs-extra": "^9.0.4",
"@types/jest": "27.0.*",
"@types/lambda-log": "^3.0.0",
"@types/node": "^20.10.5",
"@types/uuid": "^8.3.0",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"@typescript-eslint/parser": "^6.21.0",
"axios": "^1.6.7",
"axios-mock-adapter": "^1.22.0",
"eslint": "^8.3.0",
"eslint-plugin-unused-imports": "^3.0.0",
"jest": "29.6.*",
Expand All @@ -73,7 +66,7 @@
"ts-jest": "^29.1.1",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0",
"typescript": "5.3.3"
"typescript": "5.4.5"
},
"files": [
"bin",
Expand Down
30 changes: 23 additions & 7 deletions src/DI.ts → src/CliContainerFactory.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
import { Eta } from 'eta';
import { Container } from 'inversify';
import { DI } from './Constants';
import { DI } from './Constants';
import * as path from 'path/posix';
import { EtaTemplateEngine } from './Util/TemplateEngine';
import { existsSync } from 'fs-extra';
import Enquirer from 'enquirer';
import { PromptHelper } from './Util/Prompt/PromptHelper';
import { NxHelper } from './Util/Nx/NxHelper';
import { printInfo, styles } from './Util/ConsoleHelper';
import { CommonHelpers } from './Util/CommonHelpers';
import { generatorsContainerBindFactory } from './Command/Generate/Generator';

export class HcCliContainerBuilder {
export class CliContainerFactory {

private c: Container;

private constructor(cwd: string) {
this.c = new Container({ defaultScope: 'Singleton', autoBindInjectable: true });
this.bindRootDir(cwd);
}

public static create(cwd: string): HcCliContainerBuilder {
return new HcCliContainerBuilder(cwd);
public static create(cwd: string): Container {
const factory = new CliContainerFactory(cwd);
return factory.build();
}

public build(): Container {
private build(): Container {
this.bindTemplateEngine();
this.bindMonorepoHelper();
this.bindPromptHelper();
this.bindCommonHelpers();
this.bindGenerators();
return this.c;
}

Expand All @@ -34,12 +40,14 @@ export class HcCliContainerBuilder {
}

private bindMonorepoHelper(): void {
this.c.bind(DI.monorepoHelper).toService(NxHelper);
const rootDir: string = this.c.get(DI.rootDir);
if (NxHelper.isNxMonorepo(rootDir)) {
this.c.bind(DI.monorepoHelper).toService(NxHelper);
}
}

private bindTemplateEngine(): void {
const cliRootDir = path.dirname(path.dirname(__filename.replaceAll('\\', '/')));
console.log(cliRootDir);
const eta = new Eta({
views: path.join(cliRootDir, 'templates'),
autoEscape: false,
Expand All @@ -57,4 +65,12 @@ export class HcCliContainerBuilder {
});
this.c.bind(PromptHelper).toConstantValue(new PromptHelper(enquire));
}

private bindCommonHelpers() {
this.c.bind(CommonHelpers).toSelf();
}

private bindGenerators(): void {
this.c.bind(DI.generators).toDynamicValue(generatorsContainerBindFactory);
}
}
42 changes: 42 additions & 0 deletions src/Command/Generate/GenerateCommandHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Choice } from './../../Util/Prompt/EnquirerTypes';
import type { AR } from '@hexancore/common';
import { AbstractCommandHandler } from '../../Util/Cli/AbstractCommandHandler';
import { CommonHelpers } from '../../Util/CommonHelpers';
import type { AbstractCodeGenerator } from './Generator/AbstractCodeGenerator';
import { printError, PromptHelper } from '../../Util';

export interface GenerateCommandOptions {
dryRun?: true;
}

export class GenerateCommandHandler extends AbstractCommandHandler<GenerateCommandOptions> {
private generatorChoices: Choice[];

public constructor(helpers: CommonHelpers, private generators: Map<string, { choice: Choice, generator: AbstractCodeGenerator; }>) {
super(helpers);
this.generatorChoices = Array.from(generators.values()).map(g => g.choice);
}

public execute(options: GenerateCommandOptions, _args: string[]): AR<void> {
if (!this.helpers.isMonorepo()) {
printError('Current directory is not Monorepo(Nx)');
process.exit(1);
}
return this.promptGenerator().onOk((generatorType: string) => {
const g = this.generators.get(generatorType).generator;
return g.execute(options);
});
}

private promptGenerator(): AR<string> {
return this.helpers.promptOne(PromptHelper.autocomplete({
message: 'What would you like to generate?',
initial: 0,
choices: this.generatorChoices,
suggest(input: string, choices: Choice[]) {
const found = choices.filter((c) => (c.name as string).toLowerCase().startsWith(input));
return found;
}
}));
}
}
9 changes: 9 additions & 0 deletions src/Command/Generate/Generator/AbstractCodeGenerator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { CommonHelpers } from '../../../Util/CommonHelpers';
import type { GenerateCommandOptions } from '../GenerateCommandHandler';
import type { AR } from '@hexancore/common';

export abstract class AbstractCodeGenerator {
public constructor(protected helpers: CommonHelpers) { }

public abstract execute(options: GenerateCommandOptions): AR<void>;
}
96 changes: 96 additions & 0 deletions src/Command/Generate/Generator/FeatureCqrsGenerator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { type AR } from '@hexancore/common';
import { Code, StringHelper, printInfo, type ProjectFeatureInfo, CommonHelpers, PromptHelper } from '../../../Util';
import { AbstractCodeGenerator } from './AbstractCodeGenerator';

export declare type MessageType = 'Command' | 'Query' | 'Event';

interface MessageCreateParams {
type: MessageType;
featureInfo: ProjectFeatureInfo;
name: string;
context: string;

}

interface FeatureMessageGeneratorOptions {
dryRun?: true;
}

export class FeatureCqrsGenerator extends AbstractCodeGenerator {
public constructor(helpers: CommonHelpers, private messageType: MessageType) {
super(helpers);
}

public execute(options: FeatureMessageGeneratorOptions): AR<void> {
return this.prompt().onOk(params => {
params.context = StringHelper.upperFirst(params.context);
params.name = StringHelper.upperFirst(params.name);
printInfo(`Generating ${params.type.toLowerCase()} '${params.name}' in context '${params.context}' in feature '${params.featureInfo.feature}'`);
const code = this.createCode(params);
return code.save(options.dryRun);
});
}

private prompt(): AR<MessageCreateParams> {
return this.helpers.featureHelper.promptProjectAndFeature().onOk(featureInfo => this.promptMessageParams(featureInfo));
}

private promptMessageParams(featureInfo: ProjectFeatureInfo): AR<MessageCreateParams> {
return this.helpers.featureHelper.getFeatureApplicationContexts(featureInfo.project, featureInfo.feature)
.onOk(contexts => this.helpers.prompt({
context: PromptHelper.autocomplete({
message: 'Context(select or type new)',
choices: [...contexts]
}),
name: PromptHelper.simpleNameInput()
}))
.onOk(params => ({ type: this.messageType, featureInfo: featureInfo, ...params }));
}

private createCode(params: MessageCreateParams): Code {
const code = this.helpers.featureHelper.getCode(params.featureInfo, 'Generate/FeatureMessage');
this.createDirs(code, params);
this.createFiles(code, params);
return code;
}

private createDirs(code: Code, params: MessageCreateParams): void {
if (params.type === 'Event') {
return;
}

const messageDir = this.getMessageDir(params);
code.dir(`src/${code.feature}/${messageDir}`, false);
}

private createFiles(code: Code, params: MessageCreateParams): void {
const messageClassName = params.context + params.name + params.type;
const messageHandlerClassName = messageClassName + 'Handler';
const messagePath = this.getMessageDir(params);

const context = {
contextName: params.context,
name: params.name,
messageType: params.type,
messagePath,
messageClassName,
messageHandlerClassName,
};

code.srcTemplateFile('Message.ts', `${messagePath}/${messageClassName}.ts`, context);
if (params.type !== 'Event') {
code.srcTemplateFile('MessageHandler.ts', `${messagePath}/${messageHandlerClassName}.ts`, context);
const testHandlerDir = this.getTestMessageHandlerDir(params);
code.testTemplateFile('unit', 'MessageHandler.test.ts', `${testHandlerDir}/${messageHandlerClassName}.test.ts`, context);
}
}

private getMessageDir(params: MessageCreateParams): string {
return `Application/${params.context}/${params.type}/${params.name}`;

}

private getTestMessageHandlerDir(params: MessageCreateParams): string {
return `Application/${params.context}/${params.type}/`;
}
}
Loading