@comet/api-generator 8.0.0-beta.4 → 8.0.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/api-generator.js +1 -0
- package/lib/commands/generate/generate-command.js +13 -38
- package/lib/commands/generate/generateCrud/generate-crud.js +44 -57
- package/lib/commands/generate/generateCrudInput/generate-crud-input.js +12 -1
- package/lib/commands/generate/generateCrudSingle/generate-crud-single.js +6 -12
- package/lib/commands/generate/generateFiles.d.ts +7 -0
- package/lib/commands/generate/generateFiles.js +72 -0
- package/lib/commands/generate/utils/build-name-variants.d.ts +0 -1
- package/lib/commands/generate/utils/build-name-variants.js +0 -1
- package/lib/commands/generate/utils/ts-morph-helper.d.ts +1 -0
- package/lib/commands/generate/utils/ts-morph-helper.js +28 -2
- package/lib/commands/generate/utils/write-generated-file.d.ts +2 -0
- package/lib/commands/generate/utils/write-generated-file.js +67 -12
- package/lib/commands/generate/watchMode/handleChildProcess.d.ts +2 -0
- package/lib/commands/generate/watchMode/handleChildProcess.js +26 -0
- package/lib/commands/generate/watchMode/watchMode.d.ts +6 -0
- package/lib/commands/generate/watchMode/watchMode.js +79 -0
- package/package.json +6 -5
package/bin/api-generator.js
CHANGED
|
@@ -10,44 +10,19 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.generateCommand = void 0;
|
|
13
|
-
const cli_1 = require("@mikro-orm/cli");
|
|
14
|
-
const lazy_metadata_storage_1 = require("@nestjs/graphql/dist/schema-builder/storages/lazy-metadata.storage");
|
|
15
13
|
const commander_1 = require("commander");
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
const generateFiles_1 = require("./generateFiles");
|
|
15
|
+
const watchMode_1 = require("./watchMode/watchMode");
|
|
16
|
+
exports.generateCommand = new commander_1.Command("generate")
|
|
17
|
+
.action((options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
18
|
+
if (options.watch) {
|
|
19
|
+
yield (0, generateFiles_1.generateFiles)();
|
|
20
|
+
console.log("Watching for modified entities...");
|
|
21
|
+
yield (0, watchMode_1.watchMode)();
|
|
23
22
|
}
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
else {
|
|
24
|
+
(0, generateFiles_1.generateFiles)(options.file);
|
|
26
25
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
for (const name in entities) {
|
|
31
|
-
const entity = entities[name];
|
|
32
|
-
if (!entity.class) {
|
|
33
|
-
// Ignore e.g. relation entities that don't have a class
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
{
|
|
37
|
-
const generatorOptions = Reflect.getMetadata(`data:crudGeneratorOptions`, entity.class);
|
|
38
|
-
if (generatorOptions) {
|
|
39
|
-
const files = yield (0, generate_crud_1.generateCrud)(generatorOptions, entity);
|
|
40
|
-
yield (0, write_generated_files_1.writeGeneratedFiles)(files, { targetDirectory: generatorOptions.targetDirectory });
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
{
|
|
44
|
-
const generatorOptions = Reflect.getMetadata(`data:crudSingleGeneratorOptions`, entity.class);
|
|
45
|
-
if (generatorOptions) {
|
|
46
|
-
const files = yield (0, generate_crud_single_1.generateCrudSingle)(generatorOptions, entity);
|
|
47
|
-
yield (0, write_generated_files_1.writeGeneratedFiles)(files, { targetDirectory: generatorOptions.targetDirectory });
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
yield orm.close(true);
|
|
52
|
-
}
|
|
53
|
-
}));
|
|
26
|
+
}))
|
|
27
|
+
.option("-f, --file <file>", "path to entity file")
|
|
28
|
+
.option("-w, --watch", "Watch for changes");
|
|
@@ -106,7 +106,8 @@ function buildOptions(metadata, generatorOptions) {
|
|
|
106
106
|
prop.type === "DateType" ||
|
|
107
107
|
prop.type === "Date" ||
|
|
108
108
|
prop.kind === "m:1" ||
|
|
109
|
-
prop.type === "EnumArrayType"
|
|
109
|
+
prop.type === "EnumArrayType" ||
|
|
110
|
+
prop.enum));
|
|
110
111
|
const hasSortArg = crudSortProps.length > 0;
|
|
111
112
|
const hasSlugProp = metadata.props.some((prop) => prop.name == "slug");
|
|
112
113
|
const scopeProp = metadata.props.find((prop) => prop.name == "scope");
|
|
@@ -147,7 +148,7 @@ function buildOptions(metadata, generatorOptions) {
|
|
|
147
148
|
function generateFilterDto({ generatorOptions, metadata }) {
|
|
148
149
|
const { classNameSingular } = (0, build_name_variants_1.buildNameVariants)(metadata);
|
|
149
150
|
const { crudFilterProps } = buildOptions(metadata, generatorOptions);
|
|
150
|
-
|
|
151
|
+
const imports = [];
|
|
151
152
|
let enumFiltersOut = "";
|
|
152
153
|
const generatedEnumNames = new Set();
|
|
153
154
|
const generatedEnumsNames = new Set();
|
|
@@ -160,7 +161,7 @@ function generateFilterDto({ generatorOptions, metadata }) {
|
|
|
160
161
|
enumFiltersOut += `@InputType()
|
|
161
162
|
class ${enumName}EnumsFilter extends createEnumsFilter(${enumName}) {}
|
|
162
163
|
`;
|
|
163
|
-
|
|
164
|
+
imports.push({ name: enumName, importPath });
|
|
164
165
|
}
|
|
165
166
|
}
|
|
166
167
|
else if (prop.enum) {
|
|
@@ -171,7 +172,7 @@ function generateFilterDto({ generatorOptions, metadata }) {
|
|
|
171
172
|
enumFiltersOut += `@InputType()
|
|
172
173
|
class ${enumName}EnumFilter extends createEnumFilter(${enumName}) {}
|
|
173
174
|
`;
|
|
174
|
-
|
|
175
|
+
imports.push({ name: enumName, importPath });
|
|
175
176
|
}
|
|
176
177
|
}
|
|
177
178
|
});
|
|
@@ -179,7 +180,7 @@ function generateFilterDto({ generatorOptions, metadata }) {
|
|
|
179
180
|
import { Field, InputType } from "@nestjs/graphql";
|
|
180
181
|
import { Type } from "class-transformer";
|
|
181
182
|
import { IsNumber, IsOptional, IsString, ValidateNested } from "class-validator";
|
|
182
|
-
${
|
|
183
|
+
${(0, generate_imports_code_1.generateImportsCode)(imports)}
|
|
183
184
|
|
|
184
185
|
${enumFiltersOut}
|
|
185
186
|
|
|
@@ -434,9 +435,7 @@ function generateService({ generatorOptions, metadata }) {
|
|
|
434
435
|
})
|
|
435
436
|
.join(",")} }`
|
|
436
437
|
: false;
|
|
437
|
-
const serviceOut = `import { FilterQuery } from "@mikro-orm/postgresql";
|
|
438
|
-
import { InjectRepository } from "@mikro-orm/nestjs";
|
|
439
|
-
import { EntityRepository, EntityManager, raw } from "@mikro-orm/postgresql";
|
|
438
|
+
const serviceOut = `import { EntityManager, FilterQuery, raw } from "@mikro-orm/postgresql";
|
|
440
439
|
import { Injectable } from "@nestjs/common";
|
|
441
440
|
|
|
442
441
|
${(0, generate_imports_code_1.generateImportsCode)([generateEntityImport(metadata, generatorOptions.targetDirectory)])}
|
|
@@ -452,8 +451,7 @@ function generateService({ generatorOptions, metadata }) {
|
|
|
452
451
|
export class ${classNamePlural}Service {
|
|
453
452
|
${hasPositionProp
|
|
454
453
|
? `constructor(
|
|
455
|
-
|
|
456
|
-
@InjectRepository(${metadata.className}) private readonly repository: EntityRepository<${metadata.className}>,
|
|
454
|
+
protected readonly entityManager: EntityManager,
|
|
457
455
|
) {}`
|
|
458
456
|
: ""}
|
|
459
457
|
|
|
@@ -461,7 +459,7 @@ function generateService({ generatorOptions, metadata }) {
|
|
|
461
459
|
? `
|
|
462
460
|
async incrementPositions(${positionGroupProps.length ? `group: ${positionGroupType},` : ``}lowestPosition: number, highestPosition?: number) {
|
|
463
461
|
// Increment positions between newPosition (inclusive) and oldPosition (exclusive)
|
|
464
|
-
await this.
|
|
462
|
+
await this.entityManager.nativeUpdate(${metadata.className},
|
|
465
463
|
${positionGroupProps.length
|
|
466
464
|
? `{
|
|
467
465
|
$and: [
|
|
@@ -476,7 +474,7 @@ function generateService({ generatorOptions, metadata }) {
|
|
|
476
474
|
|
|
477
475
|
async decrementPositions(${positionGroupProps.length ? `group: ${positionGroupType},` : ``}lowestPosition: number, highestPosition?: number) {
|
|
478
476
|
// Decrement positions between oldPosition (exclusive) and newPosition (inclusive)
|
|
479
|
-
await this.
|
|
477
|
+
await this.entityManager.nativeUpdate(${metadata.className},
|
|
480
478
|
${positionGroupProps.length
|
|
481
479
|
? `{
|
|
482
480
|
$and: [
|
|
@@ -490,7 +488,7 @@ function generateService({ generatorOptions, metadata }) {
|
|
|
490
488
|
}
|
|
491
489
|
|
|
492
490
|
async getLastPosition(${positionGroupProps.length ? `group: ${positionGroupType}` : ``}) {
|
|
493
|
-
return this.
|
|
491
|
+
return this.entityManager.count(${metadata.className}, ${positionGroupProps.length ? `this.getPositionGroupCondition(group)` : `{}`});
|
|
494
492
|
}
|
|
495
493
|
|
|
496
494
|
${positionGroupProps.length
|
|
@@ -515,7 +513,6 @@ function generateEntityImport(targetMetadata, relativeTo) {
|
|
|
515
513
|
function generateInputHandling(options, metadata, generatorOptions) {
|
|
516
514
|
const { instanceNameSingular } = (0, build_name_variants_1.buildNameVariants)(metadata);
|
|
517
515
|
const { blockProps, scopeProp, hasPositionProp, dedicatedResolverArgProps } = buildOptions(metadata, generatorOptions);
|
|
518
|
-
const injectRepositories = [];
|
|
519
516
|
const props = metadata.props.filter((prop) => !options.excludeFields || !options.excludeFields.includes(prop.name));
|
|
520
517
|
const relationManyToOneProps = props.filter((prop) => prop.kind === "m:1");
|
|
521
518
|
const relationOneToManyProps = props.filter((prop) => prop.kind === "1:m");
|
|
@@ -531,13 +528,11 @@ function generateInputHandling(options, metadata, generatorOptions) {
|
|
|
531
528
|
const targetMeta = prop.targetMeta;
|
|
532
529
|
if (!targetMeta)
|
|
533
530
|
throw new Error("targetMeta is not set for relation");
|
|
534
|
-
injectRepositories.push(targetMeta);
|
|
535
531
|
return {
|
|
536
532
|
name: prop.name,
|
|
537
533
|
singularName: (0, pluralize_1.singular)(prop.name),
|
|
538
534
|
nullable: prop.nullable,
|
|
539
535
|
type: prop.type,
|
|
540
|
-
repositoryName: `${(0, build_name_variants_1.classNameToInstanceName)(prop.type)}Repository`,
|
|
541
536
|
};
|
|
542
537
|
});
|
|
543
538
|
const inputRelationOneToOneProps = relationOneToOneProps
|
|
@@ -546,13 +541,11 @@ function generateInputHandling(options, metadata, generatorOptions) {
|
|
|
546
541
|
const targetMeta = prop.targetMeta;
|
|
547
542
|
if (!targetMeta)
|
|
548
543
|
throw new Error("targetMeta is not set for relation");
|
|
549
|
-
injectRepositories.push(targetMeta);
|
|
550
544
|
return {
|
|
551
545
|
name: prop.name,
|
|
552
546
|
singularName: (0, pluralize_1.singular)(prop.name),
|
|
553
547
|
nullable: prop.nullable,
|
|
554
548
|
type: prop.type,
|
|
555
|
-
repositoryName: `${(0, build_name_variants_1.classNameToInstanceName)(prop.type)}Repository`,
|
|
556
549
|
targetMeta,
|
|
557
550
|
};
|
|
558
551
|
});
|
|
@@ -562,20 +555,17 @@ function generateInputHandling(options, metadata, generatorOptions) {
|
|
|
562
555
|
const targetMeta = prop.targetMeta;
|
|
563
556
|
if (!targetMeta)
|
|
564
557
|
throw new Error("targetMeta is not set for relation");
|
|
565
|
-
injectRepositories.push(targetMeta);
|
|
566
558
|
return {
|
|
567
559
|
name: prop.name,
|
|
568
560
|
singularName: (0, pluralize_1.singular)(prop.name),
|
|
569
561
|
nullable: prop.nullable,
|
|
570
562
|
type: prop.type,
|
|
571
|
-
repositoryName: `${(0, build_name_variants_1.classNameToInstanceName)(prop.type)}Repository`,
|
|
572
563
|
orphanRemoval: prop.orphanRemoval,
|
|
573
564
|
targetMeta,
|
|
574
565
|
};
|
|
575
566
|
});
|
|
576
567
|
function innerGenerateInputHandling(...args) {
|
|
577
568
|
const ret = generateInputHandling(...args);
|
|
578
|
-
injectRepositories.push(...ret.injectRepositories);
|
|
579
569
|
return ret.code;
|
|
580
570
|
}
|
|
581
571
|
const noAssignProps = [...inputRelationToManyProps, ...inputRelationManyToOneProps, ...inputRelationOneToOneProps, ...blockProps];
|
|
@@ -589,13 +579,13 @@ function generateInputHandling(options, metadata, generatorOptions) {
|
|
|
589
579
|
${options.mode == "create"
|
|
590
580
|
? dedicatedResolverArgProps
|
|
591
581
|
.map((dedicatedResolverArgProp) => {
|
|
592
|
-
return `${dedicatedResolverArgProp.name}: Reference.create(await this
|
|
582
|
+
return `${dedicatedResolverArgProp.name}: Reference.create(await this.entityManager.findOneOrFail(${dedicatedResolverArgProp.type}, ${dedicatedResolverArgProp.name})), `;
|
|
593
583
|
})
|
|
594
584
|
.join("")
|
|
595
585
|
: ""}
|
|
596
586
|
${options.mode == "create" || options.mode == "updateNested"
|
|
597
587
|
? inputRelationManyToOneProps
|
|
598
|
-
.map((prop) => `${prop.name}: ${prop.nullable ? `${prop.name}Input ? ` : ""}Reference.create(await this
|
|
588
|
+
.map((prop) => `${prop.name}: ${prop.nullable ? `${prop.name}Input ? ` : ""}Reference.create(await this.entityManager.findOneOrFail(${prop.type}, ${prop.name}Input))${prop.nullable ? ` : undefined` : ""}, `)
|
|
599
589
|
.join("")
|
|
600
590
|
: ""}
|
|
601
591
|
${options.mode == "create" || options.mode == "updateNested"
|
|
@@ -608,8 +598,8 @@ ${inputRelationToManyProps
|
|
|
608
598
|
const code = innerGenerateInputHandling({
|
|
609
599
|
mode: "updateNested",
|
|
610
600
|
inputName: `${prop.singularName}Input`,
|
|
611
|
-
// alternative `return this
|
|
612
|
-
assignEntityCode: `return this
|
|
601
|
+
// alternative `return this.entityManager.create(${prop.type}, {` requires back relation to be set
|
|
602
|
+
assignEntityCode: `return this.entityManager.assign(new ${prop.type}(), {`,
|
|
613
603
|
excludeFields: prop.targetMeta.props
|
|
614
604
|
.filter((prop) => prop.kind == "m:1" && prop.targetMeta == metadata) //filter out referencing back to this entity
|
|
615
605
|
.map((prop) => prop.name),
|
|
@@ -629,7 +619,7 @@ ${inputRelationToManyProps
|
|
|
629
619
|
else {
|
|
630
620
|
return `
|
|
631
621
|
if (${prop.name}Input) {
|
|
632
|
-
const ${prop.name} = await this
|
|
622
|
+
const ${prop.name} = await this.entityManager.find(${prop.type}, { id: ${prop.name}Input });
|
|
633
623
|
if (${prop.name}.length != ${prop.name}Input.length) throw new Error("Couldn't find all ${prop.name} that were passed as input");
|
|
634
624
|
await ${instanceNameSingular}.${prop.name}.loadItems();
|
|
635
625
|
${instanceNameSingular}.${prop.name}.set(${prop.name}.map((${prop.singularName}) => Reference.create(${prop.singularName})));
|
|
@@ -647,7 +637,7 @@ ${inputRelationOneToOneProps
|
|
|
647
637
|
${innerGenerateInputHandling({
|
|
648
638
|
mode: "updateNested",
|
|
649
639
|
inputName: `${prop.name}Input`,
|
|
650
|
-
assignEntityCode: `this
|
|
640
|
+
assignEntityCode: `this.entityManager.assign(${prop.singularName}, {`,
|
|
651
641
|
excludeFields: prop.targetMeta.props
|
|
652
642
|
.filter((prop) => prop.kind == "1:1" && prop.targetMeta == metadata) //filter out referencing back to this entity
|
|
653
643
|
.map((prop) => prop.name),
|
|
@@ -659,7 +649,7 @@ ${options.mode == "update"
|
|
|
659
649
|
.map((prop) => `if (${prop.name}Input !== undefined) {
|
|
660
650
|
${instanceNameSingular}.${prop.name} =
|
|
661
651
|
${prop.nullable ? `${prop.name}Input ? ` : ""}
|
|
662
|
-
Reference.create(await this
|
|
652
|
+
Reference.create(await this.entityManager.findOneOrFail(${prop.type}, ${prop.name}Input))
|
|
663
653
|
${prop.nullable ? ` : undefined` : ""};
|
|
664
654
|
}`)
|
|
665
655
|
.join("")
|
|
@@ -673,7 +663,7 @@ ${options.mode == "update"
|
|
|
673
663
|
.join("")
|
|
674
664
|
: ""}
|
|
675
665
|
`;
|
|
676
|
-
return { code
|
|
666
|
+
return { code };
|
|
677
667
|
}
|
|
678
668
|
function generateNestedEntityResolver({ generatorOptions, metadata }) {
|
|
679
669
|
const { classNameSingular } = (0, build_name_variants_1.buildNameVariants)(metadata);
|
|
@@ -692,7 +682,7 @@ function generateNestedEntityResolver({ generatorOptions, metadata }) {
|
|
|
692
682
|
@Resolver(() => ${metadata.className})
|
|
693
683
|
@RequiredPermission(${JSON.stringify(generatorOptions.requiredPermission)}${skipScopeCheck ? `, { skipScopeCheck: true }` : ""})
|
|
694
684
|
export class ${classNameSingular}Resolver {
|
|
695
|
-
${needsBlocksTransformer ? `constructor(
|
|
685
|
+
${needsBlocksTransformer ? `constructor(protected readonly blocksTransformer: BlocksTransformerService) {}` : ""}
|
|
696
686
|
${code}
|
|
697
687
|
}
|
|
698
688
|
`;
|
|
@@ -800,16 +790,12 @@ function generateResolver({ generatorOptions, metadata }) {
|
|
|
800
790
|
const outputRelationManyToManyProps = relationManyToManyProps.filter((prop) => (0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.name, "resolveField"));
|
|
801
791
|
const outputRelationOneToOneProps = relationOneToOneProps.filter((prop) => (0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.name, "resolveField"));
|
|
802
792
|
const imports = [];
|
|
803
|
-
const
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
if (!prop.targetMeta)
|
|
810
|
-
throw new Error("targetMeta is not set for relation");
|
|
811
|
-
return prop.targetMeta;
|
|
812
|
-
}));
|
|
793
|
+
const { code: createInputHandlingCode } = generateInputHandling({
|
|
794
|
+
mode: "create",
|
|
795
|
+
inputName: "input",
|
|
796
|
+
assignEntityCode: `const ${instanceNameSingular} = this.entityManager.create(${metadata.className}, {`,
|
|
797
|
+
}, metadata, generatorOptions);
|
|
798
|
+
const { code: updateInputHandlingCode } = generateInputHandling({ mode: "update", inputName: "input", assignEntityCode: `${instanceNameSingular}.assign({` }, metadata, generatorOptions);
|
|
813
799
|
const { imports: relationsFieldResolverImports, code: relationsFieldResolverCode, hasOutputRelations, needsBlocksTransformer, } = generateRelationsFieldResolver({
|
|
814
800
|
generatorOptions,
|
|
815
801
|
metadata,
|
|
@@ -819,7 +805,6 @@ function generateResolver({ generatorOptions, metadata }) {
|
|
|
819
805
|
if (scopeProp && scopeProp.targetMeta) {
|
|
820
806
|
imports.push(generateEntityImport(scopeProp.targetMeta, generatorOptions.targetDirectory));
|
|
821
807
|
}
|
|
822
|
-
imports.push(...injectRepositories.map((meta) => generateEntityImport(meta, generatorOptions.targetDirectory)));
|
|
823
808
|
function generateIdArg(name, metadata) {
|
|
824
809
|
if (constants_1.integerTypes.includes(metadata.properties[name].type)) {
|
|
825
810
|
return `@Args("${name}", { type: () => ID }, { transform: (value) => parseInt(value) }) ${name}: number`;
|
|
@@ -836,9 +821,7 @@ function generateResolver({ generatorOptions, metadata }) {
|
|
|
836
821
|
imports.push({ name: "RootBlockDataScalar", importPath: "@comet/cms-api" });
|
|
837
822
|
imports.push({ name: "BlocksTransformerService", importPath: "@comet/cms-api" });
|
|
838
823
|
imports.push({ name: "gqlArgsToMikroOrmQuery", importPath: "@comet/cms-api" });
|
|
839
|
-
const resolverOut = `import {
|
|
840
|
-
import { EntityRepository, EntityManager } from "@mikro-orm/postgresql";
|
|
841
|
-
import { FindOptions, ObjectQuery, Reference } from "@mikro-orm/postgresql";
|
|
824
|
+
const resolverOut = `import { EntityManager, FindOptions, ObjectQuery, Reference } from "@mikro-orm/postgresql";
|
|
842
825
|
import { Args, ID, Info, Mutation, Query, Resolver, ResolveField, Parent } from "@nestjs/graphql";
|
|
843
826
|
import { GraphQLResolveInfo } from "graphql";
|
|
844
827
|
|
|
@@ -852,19 +835,20 @@ function generateResolver({ generatorOptions, metadata }) {
|
|
|
852
835
|
@RequiredPermission(${JSON.stringify(generatorOptions.requiredPermission)}${skipScopeCheck ? `, { skipScopeCheck: true }` : ""})
|
|
853
836
|
export class ${classNameSingular}Resolver {
|
|
854
837
|
constructor(
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
${[...new Set(injectRepositories.map((meta) => meta.className))]
|
|
858
|
-
.map((type) => `@InjectRepository(${type}) private readonly ${(0, build_name_variants_1.classNameToInstanceName)(type)}Repository: EntityRepository<${type}>,`)
|
|
859
|
-
.join("")}${needsBlocksTransformer ? `private readonly blocksTransformer: BlocksTransformerService,` : ""}
|
|
838
|
+
protected readonly entityManager: EntityManager,${hasPositionProp ? `protected readonly ${instanceNamePlural}Service: ${classNamePlural}Service,` : ``}
|
|
839
|
+
${needsBlocksTransformer ? `private readonly blocksTransformer: BlocksTransformerService,` : ""}
|
|
860
840
|
) {}
|
|
861
841
|
|
|
842
|
+
${generatorOptions.single
|
|
843
|
+
? `
|
|
862
844
|
@Query(() => ${metadata.className})
|
|
863
845
|
@AffectedEntity(${metadata.className})
|
|
864
846
|
async ${instanceNameSingular}(${generateIdArg("id", metadata)}): Promise<${metadata.className}> {
|
|
865
|
-
const ${instanceNameSingular} = await this.
|
|
847
|
+
const ${instanceNameSingular} = await this.entityManager.findOneOrFail(${metadata.className}, id);
|
|
866
848
|
return ${instanceNameSingular};
|
|
867
849
|
}
|
|
850
|
+
`
|
|
851
|
+
: ""}
|
|
868
852
|
|
|
869
853
|
${hasSlugProp
|
|
870
854
|
? `
|
|
@@ -873,7 +857,7 @@ function generateResolver({ generatorOptions, metadata }) {
|
|
|
873
857
|
@Args("slug") slug: string
|
|
874
858
|
${scopeProp ? `, @Args("scope", { type: () => ${scopeProp.type} }) scope: ${scopeProp.type}` : ""}
|
|
875
859
|
): Promise<${metadata.className} | null> {
|
|
876
|
-
const ${instanceNameSingular} = await this.
|
|
860
|
+
const ${instanceNameSingular} = await this.entityManager.findOne(${metadata.className}, { slug${scopeProp ? `, scope` : ""}});
|
|
877
861
|
|
|
878
862
|
return ${instanceNameSingular} ?? null;
|
|
879
863
|
}
|
|
@@ -900,7 +884,7 @@ function generateResolver({ generatorOptions, metadata }) {
|
|
|
900
884
|
${hasOutputRelations ? `, @Info() info: GraphQLResolveInfo` : ""}
|
|
901
885
|
): Promise<Paginated${classNamePlural}> {
|
|
902
886
|
const where${hasSearchArg || hasFilterArg
|
|
903
|
-
? ` = gqlArgsToMikroOrmQuery({ ${hasSearchArg ? `search, ` : ""}${hasFilterArg ? `filter, ` : ""} }, this.
|
|
887
|
+
? ` = gqlArgsToMikroOrmQuery({ ${hasSearchArg ? `search, ` : ""}${hasFilterArg ? `filter, ` : ""} }, this.entityManager.getMetadata(${metadata.className}));`
|
|
904
888
|
: `: ObjectQuery<${metadata.className}> = {}`}
|
|
905
889
|
${scopeProp ? `where.scope = scope;` : ""}
|
|
906
890
|
${dedicatedResolverArgProps
|
|
@@ -932,7 +916,7 @@ function generateResolver({ generatorOptions, metadata }) {
|
|
|
932
916
|
}`
|
|
933
917
|
: ""}
|
|
934
918
|
|
|
935
|
-
const [entities, totalCount] = await this.
|
|
919
|
+
const [entities, totalCount] = await this.entityManager.findAndCount(${metadata.className}, where, options);
|
|
936
920
|
return new Paginated${classNamePlural}(entities, totalCount);
|
|
937
921
|
}
|
|
938
922
|
`
|
|
@@ -1003,7 +987,7 @@ function generateResolver({ generatorOptions, metadata }) {
|
|
|
1003
987
|
${generateIdArg("id", metadata)},
|
|
1004
988
|
@Args("input", { type: () => ${classNameSingular}UpdateInput }) input: ${classNameSingular}UpdateInput
|
|
1005
989
|
): Promise<${metadata.className}> {
|
|
1006
|
-
const ${instanceNameSingular} = await this.
|
|
990
|
+
const ${instanceNameSingular} = await this.entityManager.findOneOrFail(${metadata.className}, id);
|
|
1007
991
|
|
|
1008
992
|
${hasPositionProp
|
|
1009
993
|
? `
|
|
@@ -1052,7 +1036,7 @@ function generateResolver({ generatorOptions, metadata }) {
|
|
|
1052
1036
|
@Mutation(() => Boolean)
|
|
1053
1037
|
@AffectedEntity(${metadata.className})
|
|
1054
1038
|
async delete${metadata.className}(${generateIdArg("id", metadata)}): Promise<boolean> {
|
|
1055
|
-
const ${instanceNameSingular} = await this.
|
|
1039
|
+
const ${instanceNameSingular} = await this.entityManager.findOneOrFail(${metadata.className}, id);
|
|
1056
1040
|
this.entityManager.remove(${instanceNameSingular});${hasPositionProp
|
|
1057
1041
|
? `await this.${instanceNamePlural}Service.decrementPositions(${positionGroupProps.length
|
|
1058
1042
|
? `{ ${positionGroupProps
|
|
@@ -1075,8 +1059,11 @@ function generateResolver({ generatorOptions, metadata }) {
|
|
|
1075
1059
|
}
|
|
1076
1060
|
function generateCrud(generatorOptionsParam, metadata) {
|
|
1077
1061
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1078
|
-
var _a, _b, _c, _d;
|
|
1079
|
-
const generatorOptions = Object.assign(Object.assign({}, generatorOptionsParam), { create: (_a = generatorOptionsParam.create) !== null && _a !== void 0 ? _a : true, update: (_b = generatorOptionsParam.update) !== null && _b !== void 0 ? _b : true, delete: (_c = generatorOptionsParam.delete) !== null && _c !== void 0 ? _c : true, list: (_d = generatorOptionsParam.list) !== null && _d !== void 0 ? _d : true });
|
|
1062
|
+
var _a, _b, _c, _d, _e;
|
|
1063
|
+
const generatorOptions = Object.assign(Object.assign({}, generatorOptionsParam), { create: (_a = generatorOptionsParam.create) !== null && _a !== void 0 ? _a : true, update: (_b = generatorOptionsParam.update) !== null && _b !== void 0 ? _b : true, delete: (_c = generatorOptionsParam.delete) !== null && _c !== void 0 ? _c : true, list: (_d = generatorOptionsParam.list) !== null && _d !== void 0 ? _d : true, single: (_e = generatorOptionsParam.single) !== null && _e !== void 0 ? _e : true });
|
|
1064
|
+
if (!generatorOptions.create && !generatorOptions.update && !generatorOptions.delete && !generatorOptions.list && !generatorOptions.single) {
|
|
1065
|
+
throw new Error("At least one of create, update, delete, list or single must be true");
|
|
1066
|
+
}
|
|
1080
1067
|
const generatedFiles = [];
|
|
1081
1068
|
const { fileNameSingular, fileNamePlural, instanceNamePlural } = (0, build_name_variants_1.buildNameVariants)(metadata);
|
|
1082
1069
|
const { hasFilterArg, hasSortArg, argsFileName, hasPositionProp } = buildOptions(metadata, generatorOptions);
|
|
@@ -96,6 +96,7 @@ function generateCrudInput(generatorOptions_1, metadata_1) {
|
|
|
96
96
|
const fieldOptions = tsCodeRecordToString({ nullable: "true", defaultValue });
|
|
97
97
|
isOptional = true;
|
|
98
98
|
decorators.push(`@IsOptional()`);
|
|
99
|
+
imports.push({ name: "Min", importPath: "class-validator" });
|
|
99
100
|
decorators.push(`@Min(1)`);
|
|
100
101
|
decorators.push("@IsInt()");
|
|
101
102
|
decorators.push(`@Field(() => Int, ${fieldOptions})`);
|
|
@@ -395,6 +396,16 @@ function generateCrudInput(generatorOptions_1, metadata_1) {
|
|
|
395
396
|
decorators.push("@IsUUID()");
|
|
396
397
|
type = "string";
|
|
397
398
|
}
|
|
399
|
+
else if ((0, ts_morph_helper_1.getFieldDecoratorClassName)(prop.name, metadata)) {
|
|
400
|
+
//for custom mikro-orm type
|
|
401
|
+
const className = (0, ts_morph_helper_1.getFieldDecoratorClassName)(prop.name, metadata);
|
|
402
|
+
const importPath = (0, ts_morph_helper_1.findInputClassImportPath)(className, `${generatorOptions.targetDirectory}/dto`, metadata);
|
|
403
|
+
imports.push({ name: className, importPath });
|
|
404
|
+
decorators.push(`@ValidateNested()`);
|
|
405
|
+
decorators.push(`@Type(() => ${className})`);
|
|
406
|
+
decorators.push(`@Field(() => ${className}${prop.nullable ? ", { nullable: true }" : ""})`);
|
|
407
|
+
type = className;
|
|
408
|
+
}
|
|
398
409
|
else {
|
|
399
410
|
console.warn(`${prop.name}: unsupported type ${type}`);
|
|
400
411
|
continue;
|
|
@@ -434,7 +445,7 @@ function generateCrudInput(generatorOptions_1, metadata_1) {
|
|
|
434
445
|
const className = (_m = options.className) !== null && _m !== void 0 ? _m : `${metadata.className}Input`;
|
|
435
446
|
const inputOut = `import { Field, InputType, ID, Int } from "@nestjs/graphql";
|
|
436
447
|
import { Transform, Type } from "class-transformer";
|
|
437
|
-
import { IsString, IsNotEmpty, ValidateNested, IsNumber, IsBoolean, IsDate, IsOptional, IsEnum, IsUUID, IsArray, IsInt
|
|
448
|
+
import { IsString, IsNotEmpty, ValidateNested, IsNumber, IsBoolean, IsDate, IsOptional, IsEnum, IsUUID, IsArray, IsInt } from "class-validator";
|
|
438
449
|
import { GraphQLJSONObject } from "graphql-scalars";
|
|
439
450
|
import { GraphQLDate } from "graphql-scalars";
|
|
440
451
|
${(0, generate_imports_code_1.generateImportsCode)(imports)}
|
|
@@ -67,8 +67,6 @@ function generateCrudSingle(generatorOptions, metadata) {
|
|
|
67
67
|
return (0, cms_api_1.hasCrudFieldFeature)(metadata.class, prop.name, "input") && prop.type === "RootBlockType";
|
|
68
68
|
});
|
|
69
69
|
const serviceOut = `import { ObjectQuery } from "@mikro-orm/postgresql";
|
|
70
|
-
import { InjectRepository } from "@mikro-orm/nestjs";
|
|
71
|
-
import { EntityRepository } from "@mikro-orm/postgresql";
|
|
72
70
|
import { Injectable } from "@nestjs/common";
|
|
73
71
|
import { ${metadata.className} } from "${path.relative(generatorOptions.targetDirectory, metadata.path).replace(/\.ts$/, "")}";
|
|
74
72
|
|
|
@@ -78,9 +76,7 @@ function generateCrudSingle(generatorOptions, metadata) {
|
|
|
78
76
|
}
|
|
79
77
|
`;
|
|
80
78
|
generatedFiles.push({ name: `${fileNamePlural}.service.ts`, content: serviceOut, type: "service" });
|
|
81
|
-
const resolverOut = `import {
|
|
82
|
-
import { EntityRepository, EntityManager } from "@mikro-orm/postgresql";
|
|
83
|
-
import { FindOptions } from "@mikro-orm/postgresql";
|
|
79
|
+
const resolverOut = `import { FindOptions, EntityManager } from "@mikro-orm/postgresql";
|
|
84
80
|
import { Args, ID, Mutation, Query, Resolver } from "@nestjs/graphql";
|
|
85
81
|
import { RequiredPermission, SortDirection, validateNotModified } from "@comet/cms-api";
|
|
86
82
|
|
|
@@ -92,22 +88,20 @@ function generateCrudSingle(generatorOptions, metadata) {
|
|
|
92
88
|
: ""}
|
|
93
89
|
import { ${classNamePlural}Service } from "./${fileNamePlural}.service";
|
|
94
90
|
import { ${classNameSingular}Input } from "./dto/${fileNameSingular}.input";
|
|
95
|
-
import { Paginated${classNamePlural} } from "./dto/paginated-${fileNamePlural}";
|
|
96
91
|
|
|
97
92
|
@Resolver(() => ${metadata.className})
|
|
98
93
|
@RequiredPermission(${JSON.stringify(generatorOptions.requiredPermission)}${!scopeProp ? `, { skipScopeCheck: true }` : ""})
|
|
99
94
|
export class ${classNameSingular}Resolver {
|
|
100
95
|
constructor(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
@InjectRepository(${metadata.className}) private readonly repository: EntityRepository<${metadata.className}>
|
|
96
|
+
protected readonly entityManager: EntityManager,
|
|
97
|
+
protected readonly ${instanceNamePlural}Service: ${classNamePlural}Service,
|
|
104
98
|
) {}
|
|
105
99
|
|
|
106
100
|
@Query(() => ${metadata.className}, { nullable: true })
|
|
107
101
|
async ${instanceNameSingular}(
|
|
108
102
|
${scopeProp ? `@Args("scope", { type: () => ${scopeProp.type} }) scope: ${scopeProp.type},` : ""}
|
|
109
103
|
): Promise<${metadata.className} | null> {
|
|
110
|
-
const ${instanceNamePlural} = await this.
|
|
104
|
+
const ${instanceNamePlural} = await this.entityManager.find(${metadata.className}, {${scopeProp ? `scope` : ""}});
|
|
111
105
|
if (${instanceNamePlural}.length > 1) {
|
|
112
106
|
throw new Error("There must be only one ${instanceNameSingular}");
|
|
113
107
|
}
|
|
@@ -120,10 +114,10 @@ function generateCrudSingle(generatorOptions, metadata) {
|
|
|
120
114
|
${scopeProp ? `@Args("scope", { type: () => ${scopeProp.type} }) scope: ${scopeProp.type},` : ""}
|
|
121
115
|
@Args("input", { type: () => ${classNameSingular}Input }) input: ${classNameSingular}Input
|
|
122
116
|
): Promise<${metadata.className}> {
|
|
123
|
-
let ${instanceNameSingular} = await this.
|
|
117
|
+
let ${instanceNameSingular} = await this.entityManager.findOne(${metadata.className}, {${scopeProp ? `scope` : ""}});
|
|
124
118
|
|
|
125
119
|
if (!${instanceNameSingular}) {
|
|
126
|
-
${instanceNameSingular} = this.
|
|
120
|
+
${instanceNameSingular} = this.entityManager.create(${metadata.className}, {
|
|
127
121
|
...input,
|
|
128
122
|
${blockProps.length ? `${blockProps.map((prop) => `${prop.name}: input.${prop.name}.transformToBlockData()`).join(", ")}, ` : ""}
|
|
129
123
|
${scopeProp ? `scope,` : ""}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.generateFiles = void 0;
|
|
16
|
+
const node_console_1 = __importDefault(require("node:console"));
|
|
17
|
+
const cli_1 = require("@mikro-orm/cli");
|
|
18
|
+
const lazy_metadata_storage_1 = require("@nestjs/graphql/dist/schema-builder/storages/lazy-metadata.storage");
|
|
19
|
+
const generate_crud_1 = require("./generateCrud/generate-crud");
|
|
20
|
+
const generate_crud_single_1 = require("./generateCrudSingle/generate-crud-single");
|
|
21
|
+
const write_generated_files_1 = require("./utils/write-generated-files");
|
|
22
|
+
/**
|
|
23
|
+
* Generate mode for the generator.
|
|
24
|
+
*
|
|
25
|
+
* Generates CRUD files for all entities or a specific entity available at file path.
|
|
26
|
+
* @param file
|
|
27
|
+
*/
|
|
28
|
+
const generateFiles = (
|
|
29
|
+
/**
|
|
30
|
+
* File path to the entity for which to generate CRUD files.
|
|
31
|
+
*
|
|
32
|
+
* @default undefined -> generate all entities
|
|
33
|
+
*/
|
|
34
|
+
file) => __awaiter(void 0, void 0, void 0, function* () {
|
|
35
|
+
let orm = null;
|
|
36
|
+
try {
|
|
37
|
+
orm = yield cli_1.CLIHelper.getORM(undefined, undefined, { dbName: "generator" });
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
node_console_1.default.warn(e);
|
|
41
|
+
}
|
|
42
|
+
if (orm != null) {
|
|
43
|
+
const entities = orm.em.getMetadata().getAll();
|
|
44
|
+
lazy_metadata_storage_1.LazyMetadataStorage.load();
|
|
45
|
+
for (const name in entities) {
|
|
46
|
+
const entity = entities[name];
|
|
47
|
+
if (!entity.class) {
|
|
48
|
+
// Ignore e.g. relation entities that don't have a class
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (file == null || entity.path === `./${file}`) {
|
|
52
|
+
{
|
|
53
|
+
const generatorOptions = Reflect.getMetadata(`data:crudGeneratorOptions`, entity.class);
|
|
54
|
+
if (generatorOptions) {
|
|
55
|
+
node_console_1.default.log(`🚀 start generateCrud for Entity ${entity.path}`);
|
|
56
|
+
const files = yield (0, generate_crud_1.generateCrud)(generatorOptions, entity);
|
|
57
|
+
yield (0, write_generated_files_1.writeGeneratedFiles)(files, { targetDirectory: generatorOptions.targetDirectory });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
{
|
|
61
|
+
const generatorOptions = Reflect.getMetadata(`data:crudSingleGeneratorOptions`, entity.class);
|
|
62
|
+
if (generatorOptions) {
|
|
63
|
+
node_console_1.default.log(`🚀 start generateCrudSingle for Entity ${entity.path}`);
|
|
64
|
+
const files = yield (0, generate_crud_single_1.generateCrudSingle)(generatorOptions, entity);
|
|
65
|
+
yield (0, write_generated_files_1.writeGeneratedFiles)(files, { targetDirectory: generatorOptions.targetDirectory });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
exports.generateFiles = generateFiles;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { type EntityMetadata } from "@mikro-orm/postgresql";
|
|
2
|
-
export declare function classNameToInstanceName(className: string): string;
|
|
3
2
|
export declare function buildNameVariants(metadata: EntityMetadata<any>): {
|
|
4
3
|
classNameSingular: string;
|
|
5
4
|
classNamePlural: string;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.classNameToInstanceName = classNameToInstanceName;
|
|
4
3
|
exports.buildNameVariants = buildNameVariants;
|
|
5
4
|
const pluralize_1 = require("pluralize");
|
|
6
5
|
function classNameToInstanceName(className) {
|
|
@@ -8,3 +8,4 @@ export declare function findValidatorImportPath(validatorName: string, generator
|
|
|
8
8
|
export declare function findBlockName(propertyName: string, metadata: EntityMetadata<any>): string;
|
|
9
9
|
export declare function findBlockImportPath(blockName: string, targetDirectory: string, metadata: EntityMetadata<any>): string;
|
|
10
10
|
export declare function findInputClassImportPath(className: string, targetDirectory: string, metadata: EntityMetadata<any>): string;
|
|
11
|
+
export declare function getFieldDecoratorClassName(propertyName: string, metadata: EntityMetadata<any>): string | false;
|
|
@@ -40,6 +40,7 @@ exports.findValidatorImportPath = findValidatorImportPath;
|
|
|
40
40
|
exports.findBlockName = findBlockName;
|
|
41
41
|
exports.findBlockImportPath = findBlockImportPath;
|
|
42
42
|
exports.findInputClassImportPath = findInputClassImportPath;
|
|
43
|
+
exports.getFieldDecoratorClassName = getFieldDecoratorClassName;
|
|
43
44
|
const path = __importStar(require("path"));
|
|
44
45
|
const ts_morph_1 = require("ts-morph");
|
|
45
46
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
@@ -61,8 +62,14 @@ function morphTsClass(metadata) {
|
|
|
61
62
|
return tsClass;
|
|
62
63
|
}
|
|
63
64
|
function morphTsProperty(name, metadata) {
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
let currentClass = morphTsClass(metadata);
|
|
66
|
+
while (currentClass) {
|
|
67
|
+
const prop = currentClass.getProperty(name);
|
|
68
|
+
if (prop)
|
|
69
|
+
return prop;
|
|
70
|
+
currentClass = currentClass.getBaseClass();
|
|
71
|
+
}
|
|
72
|
+
throw new Error(`Property ${name} not found in ${metadata.className}`);
|
|
66
73
|
}
|
|
67
74
|
function findImportPath(importName, targetDirectory, metadata) {
|
|
68
75
|
var _a;
|
|
@@ -189,3 +196,22 @@ function findInputClassImportPath(className, targetDirectory, metadata) {
|
|
|
189
196
|
}
|
|
190
197
|
return returnImportPath;
|
|
191
198
|
}
|
|
199
|
+
function getFieldDecoratorClassName(propertyName, metadata) {
|
|
200
|
+
const definedDecorators = morphTsProperty(propertyName, metadata).getDecorators();
|
|
201
|
+
const fieldDecorator = definedDecorators.find((decorator) => decorator.getName() == "Field");
|
|
202
|
+
if (!fieldDecorator) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
const typeFnArg = fieldDecorator.getArguments()[0];
|
|
206
|
+
if (!typeFnArg)
|
|
207
|
+
throw new Error(`${propertyName}: @Field is missing argument`);
|
|
208
|
+
if (typeFnArg.getKind() != ts_morph_1.SyntaxKind.ArrowFunction)
|
|
209
|
+
throw new Error(`${propertyName}: @Field first argument must be an ArrowFunction`);
|
|
210
|
+
const typeReturningArrowFunction = typeFnArg;
|
|
211
|
+
const body = typeReturningArrowFunction.getBody();
|
|
212
|
+
if (body.getKind() != ts_morph_1.SyntaxKind.Identifier)
|
|
213
|
+
throw new Error(`${propertyName}: @Field first argument must be an ArrowFunction returning an Identifier`);
|
|
214
|
+
const identifier = body;
|
|
215
|
+
const className = identifier.getText();
|
|
216
|
+
return className;
|
|
217
|
+
}
|
|
@@ -41,29 +41,84 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
41
41
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
42
|
});
|
|
43
43
|
};
|
|
44
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
45
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
46
|
+
};
|
|
44
47
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
48
|
exports.writeGeneratedFile = writeGeneratedFile;
|
|
46
|
-
|
|
49
|
+
exports.removeUnusedImports = removeUnusedImports;
|
|
47
50
|
const fs_1 = require("fs");
|
|
48
51
|
const path = __importStar(require("path"));
|
|
52
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
49
53
|
function writeGeneratedFile(filePath, contents) {
|
|
50
54
|
return __awaiter(this, void 0, void 0, function* () {
|
|
51
55
|
const header = `// This file has been generated by comet api-generator.
|
|
52
56
|
// You may choose to use this file as scaffold by moving this file out of generated folder and removing this comment.
|
|
53
57
|
`;
|
|
54
58
|
yield fs_1.promises.mkdir(path.dirname(filePath), { recursive: true });
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const lintResult = yield eslint.lintText(header + contents, {
|
|
61
|
-
filePath,
|
|
59
|
+
const sourceFile = typescript_1.default.createSourceFile(filePath, header + contents, typescript_1.default.ScriptTarget.ES2024, true, typescript_1.default.ScriptKind.TS);
|
|
60
|
+
const result = typescript_1.default.transform(sourceFile, [removeUnusedImports()]);
|
|
61
|
+
const printer = typescript_1.default.createPrinter({
|
|
62
|
+
newLine: typescript_1.default.NewLineKind.LineFeed,
|
|
63
|
+
removeComments: false,
|
|
62
64
|
});
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
65
|
+
const prettyCode = printer.printFile(result.transformed[0]);
|
|
66
|
+
result.dispose();
|
|
67
|
+
yield fs_1.promises.writeFile(filePath, prettyCode);
|
|
67
68
|
console.log(`generated ${filePath}`);
|
|
68
69
|
});
|
|
69
70
|
}
|
|
71
|
+
function removeUnusedImports() {
|
|
72
|
+
return (context) => {
|
|
73
|
+
function visit(sourceFile) {
|
|
74
|
+
const usedIdentifiers = new Set();
|
|
75
|
+
// Step 1: Collect all used identifiers
|
|
76
|
+
function collectUsage(node) {
|
|
77
|
+
if (typescript_1.default.isIdentifier(node)) {
|
|
78
|
+
usedIdentifiers.add(node.text);
|
|
79
|
+
}
|
|
80
|
+
else if (typescript_1.default.isImportDeclaration(node)) {
|
|
81
|
+
// Skip import declarations (those identifiers are not usages)
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
typescript_1.default.forEachChild(node, collectUsage);
|
|
85
|
+
}
|
|
86
|
+
typescript_1.default.forEachChild(sourceFile, collectUsage);
|
|
87
|
+
// Step 2: Visit and update the import declarations
|
|
88
|
+
function visitor(node) {
|
|
89
|
+
if (typescript_1.default.isImportDeclaration(node) && node.importClause) {
|
|
90
|
+
const { name, namedBindings } = node.importClause;
|
|
91
|
+
const updatedBindings = [];
|
|
92
|
+
if (name && usedIdentifiers.has(name.text)) {
|
|
93
|
+
// default import is used
|
|
94
|
+
}
|
|
95
|
+
else if (name) {
|
|
96
|
+
// default import is unused
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
if (namedBindings && typescript_1.default.isNamedImports(namedBindings)) {
|
|
100
|
+
for (const specifier of namedBindings.elements) {
|
|
101
|
+
const importName = specifier.name.text;
|
|
102
|
+
if (usedIdentifiers.has(importName)) {
|
|
103
|
+
updatedBindings.push(specifier);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (updatedBindings.length === 0 && !name) {
|
|
107
|
+
// nothing left in this import
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
return typescript_1.default.factory.updateImportDeclaration(node, node.modifiers, typescript_1.default.factory.updateImportClause(node.importClause, node.importClause.isTypeOnly, name, updatedBindings.length > 0 ? typescript_1.default.factory.updateNamedImports(namedBindings, updatedBindings) : undefined), node.moduleSpecifier, node.assertClause);
|
|
111
|
+
}
|
|
112
|
+
if (!namedBindings && !name) {
|
|
113
|
+
// empty import, probably a side-effect import
|
|
114
|
+
return node;
|
|
115
|
+
}
|
|
116
|
+
return node;
|
|
117
|
+
}
|
|
118
|
+
return typescript_1.default.visitEachChild(node, visitor, context);
|
|
119
|
+
}
|
|
120
|
+
return typescript_1.default.visitNode(sourceFile, visitor);
|
|
121
|
+
}
|
|
122
|
+
return (sourceFile) => visit(sourceFile);
|
|
123
|
+
};
|
|
124
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleChildProcess = handleChildProcess;
|
|
4
|
+
function handleChildProcess(child) {
|
|
5
|
+
return new Promise((resolve, reject) => {
|
|
6
|
+
let scriptOutput = "";
|
|
7
|
+
let errorOutput = "";
|
|
8
|
+
child.stdout.setEncoding("utf8");
|
|
9
|
+
child.stdout.on("data", (data) => {
|
|
10
|
+
scriptOutput += data;
|
|
11
|
+
console.log(data);
|
|
12
|
+
});
|
|
13
|
+
child.stderr.on("data", (data) => {
|
|
14
|
+
errorOutput += data;
|
|
15
|
+
console.error(data);
|
|
16
|
+
});
|
|
17
|
+
child.on("close", (code) => {
|
|
18
|
+
if (code !== 0) {
|
|
19
|
+
reject(errorOutput);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
resolve(scriptOutput);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.watchMode = void 0;
|
|
16
|
+
const node_console_1 = __importDefault(require("node:console"));
|
|
17
|
+
const child_process_1 = require("child_process");
|
|
18
|
+
const chokidar_1 = require("chokidar");
|
|
19
|
+
const handleChildProcess_1 = require("./handleChildProcess");
|
|
20
|
+
const waitForExit = (proc) => {
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
proc.on("exit", (code, signal) => {
|
|
23
|
+
resolve({ code, signal });
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Watch mode for the generator.
|
|
29
|
+
*
|
|
30
|
+
* Watches the `src` directory for changes and triggers child processes of generator for the changed file, to process it.
|
|
31
|
+
*/
|
|
32
|
+
const watchMode = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
33
|
+
/**
|
|
34
|
+
* Collection of last ChildProcesses for each file
|
|
35
|
+
*/
|
|
36
|
+
const childProcesses = {};
|
|
37
|
+
(0, chokidar_1.watch)("./src", {
|
|
38
|
+
awaitWriteFinish: {
|
|
39
|
+
stabilityThreshold: 300,
|
|
40
|
+
pollInterval: 200,
|
|
41
|
+
},
|
|
42
|
+
ignored: (path, stats) => {
|
|
43
|
+
if (stats === null || stats === void 0 ? void 0 : stats.isFile()) {
|
|
44
|
+
return !path.endsWith(".entity.ts");
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
.on("change", (path) => __awaiter(void 0, void 0, void 0, function* () {
|
|
50
|
+
node_console_1.default.log(`🚀 File changed: ${path}`);
|
|
51
|
+
// Kill running processes for the changed file
|
|
52
|
+
if (childProcesses[path]) {
|
|
53
|
+
if (childProcesses[path].exitCode == null) {
|
|
54
|
+
childProcesses[path].kill();
|
|
55
|
+
yield waitForExit(childProcesses[path]);
|
|
56
|
+
node_console_1.default.log("💀 Killed running process for file: ", path);
|
|
57
|
+
}
|
|
58
|
+
delete childProcesses[path];
|
|
59
|
+
}
|
|
60
|
+
// Generate changed file with child process.
|
|
61
|
+
//
|
|
62
|
+
// Triggering the generator with changed files in the same process has problems with
|
|
63
|
+
// reflection and new Classes / Decorator are not recognised correctly.
|
|
64
|
+
const childProcess = (0, child_process_1.spawn)(`node ${__dirname}/../../../../bin/api-generator.js generate -f ${path}`, {
|
|
65
|
+
shell: true,
|
|
66
|
+
});
|
|
67
|
+
childProcesses[path] = childProcess;
|
|
68
|
+
try {
|
|
69
|
+
(0, handleChildProcess_1.handleChildProcess)(childProcess);
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
node_console_1.default.error(`❌ Error processing ${path} with error: ${e}`);
|
|
73
|
+
}
|
|
74
|
+
}))
|
|
75
|
+
.on("error", (error) => {
|
|
76
|
+
node_console_1.default.error(`Watcher error: ${error}`);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
exports.watchMode = watchMode;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@comet/api-generator",
|
|
3
|
-
"version": "8.0.0-beta.
|
|
3
|
+
"version": "8.0.0-beta.6",
|
|
4
4
|
"repository": {
|
|
5
5
|
"directory": "packages/api/api-generator",
|
|
6
6
|
"type": "git",
|
|
@@ -17,11 +17,12 @@
|
|
|
17
17
|
"lib/*"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
+
"chokidar": "^4.0.3",
|
|
20
21
|
"commander": "^9.5.0",
|
|
21
22
|
"pluralize": "^8.0.0",
|
|
22
23
|
"ts-morph": "^25.0.1",
|
|
23
24
|
"ts-node": "^10.9.2",
|
|
24
|
-
"@comet/cms-api": "8.0.0-beta.
|
|
25
|
+
"@comet/cms-api": "8.0.0-beta.6"
|
|
25
26
|
},
|
|
26
27
|
"devDependencies": {
|
|
27
28
|
"@mikro-orm/cli": "^6.4.10",
|
|
@@ -29,9 +30,9 @@
|
|
|
29
30
|
"@mikro-orm/postgresql": "^6.4.10",
|
|
30
31
|
"@nestjs/graphql": "^13.1.0",
|
|
31
32
|
"@types/jest": "^29.5.14",
|
|
32
|
-
"@types/node": "^22.
|
|
33
|
+
"@types/node": "^22.15.34",
|
|
33
34
|
"@types/pluralize": "^0.0.33",
|
|
34
|
-
"class-validator": "^0.14.
|
|
35
|
+
"class-validator": "^0.14.2",
|
|
35
36
|
"eslint": "^9.22.0",
|
|
36
37
|
"jest": "^29.7.0",
|
|
37
38
|
"npm-run-all2": "^5.0.2",
|
|
@@ -41,7 +42,7 @@
|
|
|
41
42
|
"ts-jest": "^29.2.6",
|
|
42
43
|
"typescript": "^5.7.3",
|
|
43
44
|
"uuid": "^11.1.0",
|
|
44
|
-
"@comet/eslint-config": "8.0.0-beta.
|
|
45
|
+
"@comet/eslint-config": "8.0.0-beta.6"
|
|
45
46
|
},
|
|
46
47
|
"peerDependencies": {
|
|
47
48
|
"@mikro-orm/cli": "^6.0.0",
|