Skip to content

Commit

Permalink
feat: add auto register feature module providers,imports
Browse files Browse the repository at this point in the history
  • Loading branch information
Mararok committed Jul 12, 2024
1 parent e352386 commit 75d6f6c
Show file tree
Hide file tree
Showing 48 changed files with 1,074 additions and 12 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
uses: hexancore/yarn-ci-install@ba9baf131eba84b6c86efb46375a530a3098bb04 # 0.1.0
- name: Lint
run: yarn lint
- name: build
run: yarn build
- name: test
uses: ./.github/actions/test
- name: build
run: yarn build
3 changes: 2 additions & 1 deletion .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # 4.1.6

- name: Yarn install
uses: hexancore/yarn-ci-install@ba9baf131eba84b6c86efb46375a530a3098bb04 # 0.1.0
- name: build
run: yarn build
- name: Test
uses: ./.github/actions/test
publish:
Expand Down
5 changes: 3 additions & 2 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ yarn.lock
.editorconfig
.eslintrc.js
.eslintignore
.jestrc.json
jest.config.ts
/config/
CONTRIBUTION.md
/tmp/
Expand All @@ -26,4 +26,5 @@ docker/
node_modules/
Makefile
.swcrc
CHANGELOG.md
CHANGELOG.md
SECURITY.md
11 changes: 11 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,23 @@ import { pathsToModuleNameMapper } from 'ts-jest';
import { compilerOptions } from './tsconfig.json';
import type { JestConfigWithTsJest } from 'ts-jest';

const rootDir = (__dirname + "/test/helper/libs/test-lib").replaceAll("\\", "/");

const jestConfig: JestConfigWithTsJest = {
preset: "ts-jest",
runner: "groups",
roots: [__dirname],
modulePaths: [__dirname],
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: "<rootDir>" }),
transform: {
'^.+\\.ts?$': ['<rootDir>/lib/Compiler/Jest', {
rootDir,
tsconfig: 'tsconfig.json',
astTransformers: {
before: [{ path: './lib/Compiler/transformer', options: { sourceRoot: rootDir + "/src" } }]
}
}]
},
testMatch: ["<rootDir>/test/**/*.test.ts"],
setupFiles: [],
setupFilesAfterEnv: ["jest-expect-message", "<rootDir>/test/config.ts"],
Expand Down
20 changes: 17 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@
"default": "./lib/Infrastructure/Http/index.js"
}
},
"./compiler/jest": {
"default": {
"types": "./lib/Compiler/Jest/index.d.ts",
"default": "./lib/Compiler/Jest/index.js"
}
},
"./compiler/transformer": {
"default": {
"types": "./lib/Compiler/transformer.d.ts",
"default": "./lib/Compiler/transformer.js"
}
},
"./testing": {
"default": {
"types": "./lib/Test/index.d.ts",
Expand All @@ -48,9 +60,10 @@
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"lint": "eslint \"{src,test}/**/*.ts\"",
"lint:fix": "eslint \"{src,test}/**/*.ts\" --fix",
"jest": "node --disable-warning=ExperimentalWarning --experimental-vm-modules node_modules/jest/bin/jest.js --config jest.config.ts",
"jest": "node --disable-warning=ExperimentalWarning --experimental-vm-modules node_modules/jest/bin/jest.js --config jest.config.ts --runInBand",
"test": "yarn run jest --runInBand",
"test:clearCache": "yarn run jest --clearCache",
"test:cc": "yarn run jest --clearCache",
"test:compiler": "yarn run build yarn && run test:cc && yarn run test",
"test:unit": "yarn run jest --runInBand --group=unit",
"test:watch": "yarn run jest --runInBand --watchAll",
"test:cov": "yarn run jest --coverage",
Expand All @@ -77,6 +90,7 @@
"deep-equal": "^2.2.3",
"fastest-validator": "^1.18.0",
"fastify": "^4.28.0",
"fdir": "^6.1.1",
"file-type": "16.5.4",
"fs-extra": "^11.1.1",
"glob": "^10.3.10",
Expand Down Expand Up @@ -122,4 +136,4 @@
"url": "https://github.com/hexancore/core/issues"
},
"packageManager": "yarn@4.1.0"
}
}
5 changes: 3 additions & 2 deletions src/Application/HcApplicationModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ const GeneralBusProvider = {

@Global()
@Module({
imports: [CqrsModule],
imports: [CqrsModule.forRoot()],
providers: [GeneralBusProvider],
exports: [GeneralBusProvider],
})
export class HcApplicationModule {}
export class HcApplicationModule {
}
92 changes: 92 additions & 0 deletions src/Compiler/Jest/HcJestTransformer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { FeatureModuleDiscoverer } from '../../Util/FeatureModuleDiscoverer';
import type { AsyncTransformer, TransformedSource } from '@jest/transform';
import { hash } from 'node:crypto';
import { TsJestTransformer, type TsJestTransformerOptions, type TsJestTransformOptions } from 'ts-jest';

export type HcJestTransformerOptions = TsJestTransformerOptions & { rootDir: string; };
export const HC_TYPESCRIPT_TRANSFORMER_MODULE_PATH = '@hexancore/core/compiler/transformer';

export class HcJestTransformer implements AsyncTransformer<HcJestTransformerOptions> {
private sourceRoot!: string;
private tsJestTransformer: TsJestTransformer;

private featureModuleDiscoveryHashMap: Map<string, string>;

protected constructor(options: HcJestTransformerOptions) {
this.processOptions(options);
this.tsJestTransformer = new TsJestTransformer(options);
this.featureModuleDiscoveryHashMap = new Map();
}

private processOptions(options: HcJestTransformerOptions) {
options.rootDir = options.rootDir.replaceAll("\\", "/");
this.sourceRoot = options.rootDir + "/src";
options.tsconfig = options.tsconfig ?? `${options.rootDir}/tsconfig.test.json`;
options.astTransformers = options.astTransformers ?? ({});
options.astTransformers.before = options.astTransformers.before ?? [];

if (!options.astTransformers.before.find((t) => typeof t !== 'string' && (t.path === HC_TYPESCRIPT_TRANSFORMER_MODULE_PATH || t.path === './lib/Compiler/transformer'))) {
options.astTransformers.before.push({
path: HC_TYPESCRIPT_TRANSFORMER_MODULE_PATH,
options: {
sourceRoot: this.sourceRoot
}
});
}
}

public static async create(options: HcJestTransformerOptions): Promise<HcJestTransformer> {
const transformer = new this(options);
await transformer.loadFeatureMap();
return transformer;
}

private async loadFeatureMap(): Promise<void> {
const discoverer = new FeatureModuleDiscoverer(this.sourceRoot);
const features = await discoverer.discoverAll();
for (const [name, discovery] of features.entries()) {
this.featureModuleDiscoveryHashMap.set(name, discovery.cacheKey);
}
}

public get canInstrument(): boolean {
return false;
}

public process(sourceText: string, sourcePath: string, options: TsJestTransformOptions): TransformedSource {
return this.tsJestTransformer.process(sourceText, sourcePath, options);
}

public processAsync(
sourceText: string,
sourcePath: string,
options: TsJestTransformOptions,
): Promise<TransformedSource> {
return this.tsJestTransformer.processAsync(sourceText, sourcePath, options as any) as any;
}

public getCacheKey(sourceText: string, sourcePath: string, transformOptions: TsJestTransformOptions): string {
const extraCacheKey = this.getExtraCacheKey(sourcePath);
const tsJestCacheKey = this.tsJestTransformer.getCacheKey(sourceText, sourcePath, transformOptions);
if (extraCacheKey) {
const combinedCacheKey = hash("sha1", tsJestCacheKey + extraCacheKey, "hex");
return combinedCacheKey;
}

return tsJestCacheKey;
}

public async getCacheKeyAsync(sourceText: string, sourcePath: string, transformOptions: TsJestTransformOptions): Promise<string> {
return this.getCacheKey(sourceText, sourcePath, transformOptions);
}

private getExtraCacheKey(sourcePath: string,): string | null {
sourcePath = sourcePath.replaceAll("\\", "/");
const extracted = FeatureModuleDiscoverer.extractFeatureNameFromPath(this.sourceRoot, sourcePath);
if (extracted) {
return this.featureModuleDiscoveryHashMap.get(extracted)!;
}

return null;
}
}
25 changes: 25 additions & 0 deletions src/Compiler/Jest/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { TsJestTransformerOptions } from "ts-jest";
import { HcJestTransformer, type HcJestTransformerOptions } from "./HcJestTransformer";
import type { AsyncTransformer } from '@jest/transform';

let cachedTransformer;

export default {
async createTransformer(options: HcJestTransformerOptions): Promise<AsyncTransformer<TsJestTransformerOptions>> {
if (!cachedTransformer) {
if (!options.rootDir) {
throw new Error("Undefined rootDir in jest.config.ts `transform`");
}

const t = await HcJestTransformer.create(options);
cachedTransformer = {
process: t.process.bind(t),
processAsync: t.processAsync.bind(t),
getCacheKey: t.getCacheKey.bind(t),
getCacheKeyAsync: t.getCacheKeyAsync.bind(t)
};
}

return cachedTransformer;
},
};
59 changes: 59 additions & 0 deletions src/Compiler/Test/TsTransformerTestHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { readFileSync } from "node:fs";
import path from "node:path";
import * as ts from "typescript";
export class TsTransformerTestHelper {
public constructor(private compilerOptions: ts.CompilerOptions) {

}

public static createFromTsConfig(tsConfigFilePath: string): TsTransformerTestHelper {
const tsConfig = this.loadTsConfig(tsConfigFilePath);
return new this(tsConfig.options);
}

private static loadTsConfig(tsConfigFilePath: string): ts.ParsedCommandLine {
const configFile = ts.readConfigFile(tsConfigFilePath, ts.sys.readFile);
if (configFile.error) {
throw new Error(`Error reading tsconfig.json: ${configFile.error.messageText}`);
}
const parsedConfig = ts.parseJsonConfigFileContent(
configFile.config,
ts.sys,
path.dirname(tsConfigFilePath)
);

return parsedConfig;
}

public createSourceFileFromExisting(existingFilePath: string, filePath?: string): ts.SourceFile {
const code = readFileSync(existingFilePath).toString();
return this.createSourceFile(filePath ?? existingFilePath, code);
}

public createSourceFile(filePath: string, code: string): ts.SourceFile {
return ts.createSourceFile(
filePath,
code,
this.compilerOptions.target ?? ts.ScriptTarget.Latest
);
}

public createTransformerFactory(transformer: (sourceFile: ts.SourceFile) => ts.SourceFile): ts.TransformerFactory<ts.SourceFile> {
return (context: ts.TransformationContext) => transformer;
}

public transformAndReturnAsString(sourceFile: ts.SourceFile, transformers: ts.TransformerFactory<ts.SourceFile>[]): string {
const transformResult = ts.transform(sourceFile, transformers, this.compilerOptions);

const transformedSourceFile = transformResult.transformed[0];
const printer = ts.createPrinter();

const result = printer.printNode(
ts.EmitHint.SourceFile,
transformedSourceFile,
sourceFile
);

return result;
}
}
Loading

0 comments on commit 75d6f6c

Please sign in to comment.