@lark-apaas/devtool-kits 1.1.0 → 1.2.0-alpha.0
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/dist/index.cjs +679 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +683 -15
- package/dist/index.js.map +1 -1
- package/dist/template/types.ts +8 -21
- package/package.json +3 -2
package/dist/index.cjs
CHANGED
|
@@ -36,6 +36,7 @@ __export(index_exports, {
|
|
|
36
36
|
createOpenapiMiddleware: () => createOpenapiMiddleware,
|
|
37
37
|
handleDevProxyError: () => handleDevProxyError,
|
|
38
38
|
normalizeBasePath: () => normalizeBasePath,
|
|
39
|
+
parseAndGenerateNestResourceTemplate: () => parseAndGenerateNestResourceTemplate,
|
|
39
40
|
postprocessDrizzleSchema: () => postprocessDrizzleSchema,
|
|
40
41
|
registerMiddlewares: () => registerMiddlewares
|
|
41
42
|
});
|
|
@@ -554,6 +555,673 @@ function postprocessDrizzleSchema(targetPath) {
|
|
|
554
555
|
}
|
|
555
556
|
__name(postprocessDrizzleSchema, "postprocessDrizzleSchema");
|
|
556
557
|
|
|
558
|
+
// src/helpers/gen-nest-resource/utils.ts
|
|
559
|
+
function mapDrizzleTypeToTS(field) {
|
|
560
|
+
const typeMap = {
|
|
561
|
+
// String types
|
|
562
|
+
char: "string",
|
|
563
|
+
varchar: "string",
|
|
564
|
+
text: "string",
|
|
565
|
+
// Numeric types
|
|
566
|
+
smallint: "number",
|
|
567
|
+
integer: "number",
|
|
568
|
+
int: "number",
|
|
569
|
+
bigint: "string",
|
|
570
|
+
serial: "number",
|
|
571
|
+
smallserial: "number",
|
|
572
|
+
bigserial: "string",
|
|
573
|
+
// Decimal types
|
|
574
|
+
decimal: "string",
|
|
575
|
+
numeric: "string",
|
|
576
|
+
real: "number",
|
|
577
|
+
doublePrecision: "number",
|
|
578
|
+
// Boolean
|
|
579
|
+
boolean: "boolean",
|
|
580
|
+
// Date/Time types
|
|
581
|
+
timestamp: "Date",
|
|
582
|
+
timestamptz: "Date",
|
|
583
|
+
date: "Date",
|
|
584
|
+
time: "string",
|
|
585
|
+
timetz: "string",
|
|
586
|
+
interval: "string",
|
|
587
|
+
// UUID
|
|
588
|
+
uuid: "string",
|
|
589
|
+
// JSON types
|
|
590
|
+
json: "any",
|
|
591
|
+
jsonb: "any",
|
|
592
|
+
// Binary
|
|
593
|
+
bytea: "Buffer",
|
|
594
|
+
// Network types
|
|
595
|
+
inet: "string",
|
|
596
|
+
cidr: "string",
|
|
597
|
+
macaddr: "string",
|
|
598
|
+
macaddr8: "string",
|
|
599
|
+
// Geometric types
|
|
600
|
+
point: "{ x: number; y: number }",
|
|
601
|
+
line: "string",
|
|
602
|
+
lseg: "string",
|
|
603
|
+
box: "string",
|
|
604
|
+
path: "string",
|
|
605
|
+
polygon: "string",
|
|
606
|
+
circle: "string",
|
|
607
|
+
// Array types (handled by isArray flag)
|
|
608
|
+
array: "any[]",
|
|
609
|
+
// Custom types
|
|
610
|
+
customType: "any",
|
|
611
|
+
customTimestamptz: "Date",
|
|
612
|
+
userProfile: "string",
|
|
613
|
+
fileAttachment: "FileAttachment",
|
|
614
|
+
// Enum (handled separately)
|
|
615
|
+
pgEnum: "string"
|
|
616
|
+
};
|
|
617
|
+
let baseType = typeMap[field.type] || "any";
|
|
618
|
+
if (field.isArray) {
|
|
619
|
+
baseType = baseType.endsWith("[]") ? baseType : `${baseType}[]`;
|
|
620
|
+
}
|
|
621
|
+
if (field.enumValues && field.enumValues.length > 0) {
|
|
622
|
+
baseType = field.enumValues.map((v) => `'${v}'`).join(" | ");
|
|
623
|
+
}
|
|
624
|
+
return baseType;
|
|
625
|
+
}
|
|
626
|
+
__name(mapDrizzleTypeToTS, "mapDrizzleTypeToTS");
|
|
627
|
+
function toPascalCase(str) {
|
|
628
|
+
return str.replace(/([a-z])([A-Z])/g, "$1 $2").split(/[-_\s]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
629
|
+
}
|
|
630
|
+
__name(toPascalCase, "toPascalCase");
|
|
631
|
+
function toKebabCase(str) {
|
|
632
|
+
return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase().replace(/[_\s]/g, "-");
|
|
633
|
+
}
|
|
634
|
+
__name(toKebabCase, "toKebabCase");
|
|
635
|
+
|
|
636
|
+
// src/helpers/gen-nest-resource/generator.ts
|
|
637
|
+
function generateDTO(table) {
|
|
638
|
+
const className = toPascalCase(table.variableName);
|
|
639
|
+
let dto = `// \u8BF7\u4FEE\u6539\u8BE5\u6587\u4EF6\u4EE3\u7801\u4EE5\u6EE1\u8DB3\u9700\u6C42
|
|
640
|
+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
641
|
+
import { IsDefined, IsNumber, IsOptional, IsString, MaxLength, IsInt, IsBoolean, IsUUID, IsDate, IsObject, IsArray } from 'class-validator';
|
|
642
|
+
import { Type } from 'class-transformer';
|
|
643
|
+
import { FileAttachment } from '../../../database/schema';
|
|
644
|
+
|
|
645
|
+
`;
|
|
646
|
+
dto += `export class Create${className}Dto {
|
|
647
|
+
`;
|
|
648
|
+
for (const field of table.fields) {
|
|
649
|
+
if (field.isPrimaryKey || field.name === "id" || field.name.startsWith("_") || field.name.startsWith("created") || field.name.startsWith("updated")) {
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
const tsType = mapDrizzleTypeToTS(field);
|
|
653
|
+
const optional = field.nullable || field.hasDefault ? "?" : "";
|
|
654
|
+
const decorators = generateValidationDecorators(field);
|
|
655
|
+
if (decorators) {
|
|
656
|
+
dto += decorators;
|
|
657
|
+
}
|
|
658
|
+
dto += ` ${field.name}${optional}: ${tsType};
|
|
659
|
+
|
|
660
|
+
`;
|
|
661
|
+
}
|
|
662
|
+
dto += "}\n\n";
|
|
663
|
+
dto += `export class Update${className}Dto {
|
|
664
|
+
`;
|
|
665
|
+
for (const field of table.fields) {
|
|
666
|
+
if (field.name.startsWith("_") || field.name.startsWith("created") || field.name.startsWith("updated") || field.isPrimaryKey || field.name === "id") {
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
const tsType = mapDrizzleTypeToTS(field);
|
|
670
|
+
const decorators = generateValidationDecorators(field, {
|
|
671
|
+
isUpdate: true
|
|
672
|
+
});
|
|
673
|
+
if (decorators) {
|
|
674
|
+
dto += decorators;
|
|
675
|
+
}
|
|
676
|
+
dto += ` ${field.name}?: ${tsType};
|
|
677
|
+
|
|
678
|
+
`;
|
|
679
|
+
}
|
|
680
|
+
dto += "}\n\n";
|
|
681
|
+
dto += `export class ${className}ResponseDto {
|
|
682
|
+
`;
|
|
683
|
+
for (const field of table.fields) {
|
|
684
|
+
const tsType = mapDrizzleTypeToTS(field);
|
|
685
|
+
const optional = field.nullable ? "?" : "";
|
|
686
|
+
const decorators = generateValidationDecorators(field, {
|
|
687
|
+
isResponse: true
|
|
688
|
+
});
|
|
689
|
+
if (decorators) {
|
|
690
|
+
dto += decorators;
|
|
691
|
+
}
|
|
692
|
+
dto += ` ${field.name}${optional}: ${tsType};
|
|
693
|
+
|
|
694
|
+
`;
|
|
695
|
+
}
|
|
696
|
+
dto += "}\n";
|
|
697
|
+
return dto;
|
|
698
|
+
}
|
|
699
|
+
__name(generateDTO, "generateDTO");
|
|
700
|
+
function generateValidationDecorators(field, { isUpdate = false, isResponse = false } = {}) {
|
|
701
|
+
let decorators = " // \u8BF7\u6309\u7528\u6237\u9700\u6C42\u4FEE\u6539\u4EE5\u4E0B\u88C5\u9970\u5668\u6CE8\u91CA\n";
|
|
702
|
+
if (field.nullable || !isResponse && field.hasDefault || isUpdate) {
|
|
703
|
+
decorators += ` @ApiPropertyOptional({ description: '${field.comment || field.name}' })
|
|
704
|
+
`;
|
|
705
|
+
if (isResponse) {
|
|
706
|
+
return decorators;
|
|
707
|
+
}
|
|
708
|
+
decorators += " @IsOptional()\n";
|
|
709
|
+
} else {
|
|
710
|
+
decorators += ` @ApiProperty({ description: '${field.comment || field.name}' })
|
|
711
|
+
`;
|
|
712
|
+
if (isResponse) {
|
|
713
|
+
return decorators;
|
|
714
|
+
}
|
|
715
|
+
decorators += " @IsDefined()\n";
|
|
716
|
+
}
|
|
717
|
+
switch (field.type) {
|
|
718
|
+
case "varchar":
|
|
719
|
+
case "char":
|
|
720
|
+
case "text":
|
|
721
|
+
decorators += " @IsString()\n";
|
|
722
|
+
if (field.length) {
|
|
723
|
+
decorators += ` @MaxLength(${field.length})
|
|
724
|
+
`;
|
|
725
|
+
}
|
|
726
|
+
break;
|
|
727
|
+
case "integer":
|
|
728
|
+
case "smallint":
|
|
729
|
+
case "serial":
|
|
730
|
+
case "smallserial":
|
|
731
|
+
decorators += " @IsInt()\n";
|
|
732
|
+
break;
|
|
733
|
+
case "decimal":
|
|
734
|
+
case "numeric":
|
|
735
|
+
case "real":
|
|
736
|
+
case "doublePrecision":
|
|
737
|
+
decorators += " @IsNumber()\n";
|
|
738
|
+
break;
|
|
739
|
+
case "boolean":
|
|
740
|
+
decorators += " @IsBoolean()\n";
|
|
741
|
+
break;
|
|
742
|
+
case "uuid":
|
|
743
|
+
decorators += " @IsUUID()\n";
|
|
744
|
+
break;
|
|
745
|
+
case "timestamp":
|
|
746
|
+
case "timestamptz":
|
|
747
|
+
case "date":
|
|
748
|
+
case "customTimestamptz":
|
|
749
|
+
decorators += " @IsDate()\n";
|
|
750
|
+
break;
|
|
751
|
+
case "json":
|
|
752
|
+
case "jsonb":
|
|
753
|
+
decorators += " @IsObject()\n";
|
|
754
|
+
break;
|
|
755
|
+
}
|
|
756
|
+
if (field.isArray) {
|
|
757
|
+
decorators += " @IsArray()\n";
|
|
758
|
+
}
|
|
759
|
+
return decorators;
|
|
760
|
+
}
|
|
761
|
+
__name(generateValidationDecorators, "generateValidationDecorators");
|
|
762
|
+
function generateController(table) {
|
|
763
|
+
const className = toPascalCase(table.variableName);
|
|
764
|
+
const routePath = toKebabCase(table.variableName);
|
|
765
|
+
const pkField = table.fields.find((f) => f.isPrimaryKey);
|
|
766
|
+
const pkType = pkField ? mapDrizzleTypeToTS(pkField) : "string";
|
|
767
|
+
const pkName = pkField ? pkField.name : "id";
|
|
768
|
+
const controller = `
|
|
769
|
+
// \u8BF7\u4FEE\u6539\u8BE5\u6587\u4EF6\u4EE3\u7801\u4EE5\u6EE1\u8DB3\u9700\u6C42
|
|
770
|
+
import {
|
|
771
|
+
Controller,
|
|
772
|
+
Get,
|
|
773
|
+
Post,
|
|
774
|
+
Put,
|
|
775
|
+
Delete,
|
|
776
|
+
Body,
|
|
777
|
+
Param,
|
|
778
|
+
Query,
|
|
779
|
+
} from '@nestjs/common';
|
|
780
|
+
import {
|
|
781
|
+
ApiTags,
|
|
782
|
+
ApiOperation,
|
|
783
|
+
ApiOkResponse,
|
|
784
|
+
ApiCreatedResponse,
|
|
785
|
+
} from '@nestjs/swagger';
|
|
786
|
+
import {
|
|
787
|
+
Create${className}Dto,
|
|
788
|
+
Update${className}Dto,
|
|
789
|
+
${className}ResponseDto
|
|
790
|
+
} from './dtos/${routePath}.dto';
|
|
791
|
+
import { ${className}Service } from './${routePath}.service';
|
|
792
|
+
|
|
793
|
+
@ApiTags('${toPascalCase(table.variableName)}')
|
|
794
|
+
@Controller('api/${routePath}')
|
|
795
|
+
export class ${className}Controller {
|
|
796
|
+
constructor(private readonly ${table.variableName}Service: ${className}Service) {}
|
|
797
|
+
|
|
798
|
+
@Post()
|
|
799
|
+
@ApiOperation({
|
|
800
|
+
summary: '\u521B\u5EFA\u4E00\u6761\u8BB0\u5F55\uFF08\u6A21\u677F\u5185\u5BB9\uFF0C\u8BF7\u4FEE\u6539\u6211\uFF09',
|
|
801
|
+
description: '\u521B\u5EFA\u4E00\u6761\u8BB0\u5F55\uFF08\u6A21\u677F\u5185\u5BB9\uFF0C\u8BF7\u4FEE\u6539\u6211\uFF09',
|
|
802
|
+
})
|
|
803
|
+
@ApiCreatedResponse({
|
|
804
|
+
description: '\u6210\u529F\u521B\u5EFA\u4E00\u6761\u8BB0\u5F55',
|
|
805
|
+
type: ${className}ResponseDto,
|
|
806
|
+
})
|
|
807
|
+
async create(
|
|
808
|
+
@Body() createDto: Create${className}Dto
|
|
809
|
+
): Promise<${className}ResponseDto> {
|
|
810
|
+
return this.${table.variableName}Service.create(createDto);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
@ApiOperation({
|
|
814
|
+
summary: '\u6839\u636E\u4E3B\u952E\u67E5\u8BE2\u4E00\u6761\u8BB0\u5F55\uFF08\u6A21\u677F\u5185\u5BB9\uFF0C\u8BF7\u4FEE\u6539\u6211\uFF09',
|
|
815
|
+
description: '\u6839\u636E\u4E3B\u952E\u67E5\u8BE2\u4E00\u6761\u8BB0\u5F55\uFF08\u6A21\u677F\u5185\u5BB9\uFF0C\u8BF7\u4FEE\u6539\u6211\uFF09',
|
|
816
|
+
})
|
|
817
|
+
@ApiOkResponse({
|
|
818
|
+
description: '\u6210\u529F\u67E5\u8BE2\u4E00\u6761\u8BB0\u5F55',
|
|
819
|
+
type: ${className}ResponseDto,
|
|
820
|
+
})
|
|
821
|
+
@Get(':${pkName}')
|
|
822
|
+
async findOne(
|
|
823
|
+
@Param('${pkName}') ${pkName}: ${pkType}
|
|
824
|
+
): Promise<${className}ResponseDto> {
|
|
825
|
+
return this.${table.variableName}Service.findOne(${pkName});
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
@ApiOperation({
|
|
829
|
+
summary: '\u6839\u636E\u4E3B\u952E\u66F4\u65B0\u4E00\u6761\u8BB0\u5F55\uFF08\u6A21\u677F\u5185\u5BB9\uFF0C\u8BF7\u4FEE\u6539\u6211\uFF09',
|
|
830
|
+
description: '\u6839\u636E\u4E3B\u952E\u66F4\u65B0\u4E00\u6761\u8BB0\u5F55\uFF08\u6A21\u677F\u5185\u5BB9\uFF0C\u8BF7\u4FEE\u6539\u6211\uFF09',
|
|
831
|
+
})
|
|
832
|
+
@ApiOkResponse({
|
|
833
|
+
description: '\u6210\u529F\u66F4\u65B0\u4E00\u6761\u8BB0\u5F55',
|
|
834
|
+
type: ${className}ResponseDto,
|
|
835
|
+
})
|
|
836
|
+
@Put(':${pkName}')
|
|
837
|
+
async update(
|
|
838
|
+
@Param('${pkName}') ${pkName}: ${pkType},
|
|
839
|
+
@Body() updateDto: Update${className}Dto
|
|
840
|
+
): Promise<${className}ResponseDto> {
|
|
841
|
+
return this.${table.variableName}Service.update(${pkName}, updateDto);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
@ApiOperation({
|
|
845
|
+
summary: '\u6839\u636E\u4E3B\u952E\u5220\u9664\u4E00\u6761\u8BB0\u5F55\uFF08\u6A21\u677F\u5185\u5BB9\uFF0C\u8BF7\u4FEE\u6539\u6211\uFF09',
|
|
846
|
+
description: '\u6839\u636E\u4E3B\u952E\u5220\u9664\u4E00\u6761\u8BB0\u5F55\uFF08\u6A21\u677F\u5185\u5BB9\uFF0C\u8BF7\u4FEE\u6539\u6211\uFF09',
|
|
847
|
+
})
|
|
848
|
+
@ApiOkResponse({
|
|
849
|
+
description: '\u6210\u529F\u5220\u9664\u4E00\u6761\u8BB0\u5F55',
|
|
850
|
+
})
|
|
851
|
+
@Delete(':${pkName}')
|
|
852
|
+
async remove(
|
|
853
|
+
@Param('${pkName}') ${pkName}: ${pkType}
|
|
854
|
+
): Promise<void> {
|
|
855
|
+
return this.${table.variableName}Service.remove(${pkName});
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
`;
|
|
859
|
+
return controller;
|
|
860
|
+
}
|
|
861
|
+
__name(generateController, "generateController");
|
|
862
|
+
function generateService(table) {
|
|
863
|
+
const className = toPascalCase(table.variableName);
|
|
864
|
+
const routePath = toKebabCase(table.variableName);
|
|
865
|
+
const pkField = table.fields.find((f) => f.isPrimaryKey);
|
|
866
|
+
const pkType = pkField ? mapDrizzleTypeToTS(pkField) : "string";
|
|
867
|
+
const pkName = pkField ? pkField.name : "id";
|
|
868
|
+
const service = `
|
|
869
|
+
// \u8BF7\u4FEE\u6539\u8BE5\u6587\u4EF6\u4EE3\u7801\u4EE5\u6EE1\u8DB3\u9700\u6C42
|
|
870
|
+
import { Injectable, Inject, Logger, NotFoundException } from '@nestjs/common';
|
|
871
|
+
import { eq } from 'drizzle-orm';
|
|
872
|
+
import { DRIZZLE_DATABASE, type PostgresJsDatabase } from '@lark-apaas/fullstack-nestjs-core';
|
|
873
|
+
import { ${table.variableName} } from '../../database/schema';
|
|
874
|
+
import {
|
|
875
|
+
Create${className}Dto,
|
|
876
|
+
Update${className}Dto,
|
|
877
|
+
${className}ResponseDto
|
|
878
|
+
} from './dtos/${routePath}.dto';
|
|
879
|
+
|
|
880
|
+
@Injectable()
|
|
881
|
+
export class ${className}Service {
|
|
882
|
+
private readonly logger = new Logger(${className}Service.name);
|
|
883
|
+
|
|
884
|
+
constructor(@Inject(DRIZZLE_DATABASE) private readonly db: PostgresJsDatabase) {}
|
|
885
|
+
|
|
886
|
+
async create(createDto: Create${className}Dto): Promise<${className}ResponseDto> {
|
|
887
|
+
const [result] = await this.db
|
|
888
|
+
.insert(${table.variableName})
|
|
889
|
+
.values(createDto)
|
|
890
|
+
.returning();
|
|
891
|
+
|
|
892
|
+
this.logger.log(\`Created ${className} with ${pkName} \${result.${pkName}}\`);
|
|
893
|
+
|
|
894
|
+
return result;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
async findAll(options?: { page?: number; limit?: number }): Promise<${className}ResponseDto[]> {
|
|
898
|
+
const { page = 1, limit = 10 } = options || {};
|
|
899
|
+
const offset = (page - 1) * limit;
|
|
900
|
+
|
|
901
|
+
return this.db
|
|
902
|
+
.select()
|
|
903
|
+
.from(${table.variableName})
|
|
904
|
+
.limit(limit)
|
|
905
|
+
.offset(offset);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
async findOne(${pkName}: ${pkType}): Promise<${className}ResponseDto> {
|
|
909
|
+
const [result] = await this.db
|
|
910
|
+
.select()
|
|
911
|
+
.from(${table.variableName})
|
|
912
|
+
.where(eq(${table.variableName}.${pkName}, ${pkName}))
|
|
913
|
+
.limit(1);
|
|
914
|
+
|
|
915
|
+
if (!result) {
|
|
916
|
+
throw new NotFoundException(\`${className} with ${pkName} \${${pkName}} not found\`);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
return result;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
async update(${pkName}: ${pkType}, updateDto: Update${className}Dto): Promise<${className}ResponseDto> {
|
|
923
|
+
const [result] = await this.db
|
|
924
|
+
.update(${table.variableName})
|
|
925
|
+
.set(updateDto)
|
|
926
|
+
.where(eq(${table.variableName}.${pkName}, ${pkName}))
|
|
927
|
+
.returning();
|
|
928
|
+
|
|
929
|
+
if (!result) {
|
|
930
|
+
throw new NotFoundException(\`${className} with ${pkName} \${${pkName}} not found\`);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
return result;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
async remove(${pkName}: ${pkType}): Promise<void> {
|
|
937
|
+
const result = await this.db
|
|
938
|
+
.delete(${table.variableName})
|
|
939
|
+
.where(eq(${table.variableName}.${pkName}, ${pkName}))
|
|
940
|
+
.returning();
|
|
941
|
+
|
|
942
|
+
if (result.length === 0) {
|
|
943
|
+
throw new NotFoundException(\`${className} with ${pkName} \${${pkName}} not found\`);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
this.logger.log(\`Deleted ${className} with ${pkName} \${${pkName}}\`);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
`;
|
|
950
|
+
return service;
|
|
951
|
+
}
|
|
952
|
+
__name(generateService, "generateService");
|
|
953
|
+
function generateModule(table) {
|
|
954
|
+
const className = toPascalCase(table.variableName);
|
|
955
|
+
const routePath = toKebabCase(table.variableName);
|
|
956
|
+
const module2 = `
|
|
957
|
+
import { Module } from '@nestjs/common';
|
|
958
|
+
import { ${className}Controller } from './${routePath}.controller';
|
|
959
|
+
import { ${className}Service } from './${routePath}.service';
|
|
960
|
+
|
|
961
|
+
@Module({
|
|
962
|
+
controllers: [${className}Controller],
|
|
963
|
+
providers: [${className}Service],
|
|
964
|
+
})
|
|
965
|
+
export class ${className}Module {}
|
|
966
|
+
`;
|
|
967
|
+
return module2;
|
|
968
|
+
}
|
|
969
|
+
__name(generateModule, "generateModule");
|
|
970
|
+
|
|
971
|
+
// src/helpers/gen-nest-resource/schema-parser.ts
|
|
972
|
+
var import_ts_morph = require("ts-morph");
|
|
973
|
+
var DrizzleSchemaParser = class DrizzleSchemaParser2 {
|
|
974
|
+
static {
|
|
975
|
+
__name(this, "DrizzleSchemaParser");
|
|
976
|
+
}
|
|
977
|
+
project;
|
|
978
|
+
constructor(projectOptions) {
|
|
979
|
+
this.project = new import_ts_morph.Project(projectOptions);
|
|
980
|
+
}
|
|
981
|
+
parseSchemaFile(filePath) {
|
|
982
|
+
const sourceFile = this.project.addSourceFileAtPath(filePath);
|
|
983
|
+
const tables = [];
|
|
984
|
+
const variableStatements = sourceFile.getVariableStatements();
|
|
985
|
+
for (const statement of variableStatements) {
|
|
986
|
+
const declarations = statement.getDeclarations();
|
|
987
|
+
for (const declaration of declarations) {
|
|
988
|
+
const initializer = declaration.getInitializer();
|
|
989
|
+
if (initializer && import_ts_morph.Node.isCallExpression(initializer)) {
|
|
990
|
+
const expression = initializer.getExpression();
|
|
991
|
+
if (expression.getText() === "pgTable") {
|
|
992
|
+
const tableInfo = this.parsePgTable(declaration.getName(), initializer);
|
|
993
|
+
if (tableInfo) {
|
|
994
|
+
tables.push(tableInfo);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
return tables;
|
|
1001
|
+
}
|
|
1002
|
+
parsePgTable(variableName, callExpr) {
|
|
1003
|
+
const args = callExpr.getArguments();
|
|
1004
|
+
if (args.length < 2) {
|
|
1005
|
+
return null;
|
|
1006
|
+
}
|
|
1007
|
+
const tableName = args[0].getText().replace(/['"]/g, "");
|
|
1008
|
+
const fieldsArg = args[1];
|
|
1009
|
+
if (!import_ts_morph.Node.isObjectLiteralExpression(fieldsArg)) {
|
|
1010
|
+
return null;
|
|
1011
|
+
}
|
|
1012
|
+
const fields = [];
|
|
1013
|
+
const properties = fieldsArg.getProperties();
|
|
1014
|
+
for (const prop of properties) {
|
|
1015
|
+
if (import_ts_morph.Node.isPropertyAssignment(prop)) {
|
|
1016
|
+
const fieldName = prop.getName();
|
|
1017
|
+
const initializer = prop.getInitializer();
|
|
1018
|
+
const leadingComments = prop.getLeadingCommentRanges();
|
|
1019
|
+
let comment;
|
|
1020
|
+
if (leadingComments.length > 0) {
|
|
1021
|
+
comment = leadingComments.map((c) => c.getText()).join("\n").replace(/\/\//g, "").trim();
|
|
1022
|
+
}
|
|
1023
|
+
if (initializer && import_ts_morph.Node.isCallExpression(initializer)) {
|
|
1024
|
+
const fieldInfo = this.parseField(fieldName, initializer, comment);
|
|
1025
|
+
fields.push(fieldInfo);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
return {
|
|
1030
|
+
tableName,
|
|
1031
|
+
variableName,
|
|
1032
|
+
fields
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
parseField(fieldName, callExpr, comment) {
|
|
1036
|
+
const fieldInfo = {
|
|
1037
|
+
name: fieldName,
|
|
1038
|
+
columnName: fieldName,
|
|
1039
|
+
type: "",
|
|
1040
|
+
nullable: true,
|
|
1041
|
+
hasDefault: false,
|
|
1042
|
+
notNull: false,
|
|
1043
|
+
isPrimaryKey: false,
|
|
1044
|
+
isUnique: false,
|
|
1045
|
+
isArray: false,
|
|
1046
|
+
comment
|
|
1047
|
+
};
|
|
1048
|
+
this.parseBaseType(callExpr, fieldInfo);
|
|
1049
|
+
this.parseCallChain(callExpr, fieldInfo);
|
|
1050
|
+
return fieldInfo;
|
|
1051
|
+
}
|
|
1052
|
+
parseBaseType(callExpr, fieldInfo) {
|
|
1053
|
+
let current = callExpr;
|
|
1054
|
+
let baseCall = null;
|
|
1055
|
+
while (import_ts_morph.Node.isCallExpression(current)) {
|
|
1056
|
+
baseCall = current;
|
|
1057
|
+
const expression2 = current.getExpression();
|
|
1058
|
+
if (import_ts_morph.Node.isPropertyAccessExpression(expression2)) {
|
|
1059
|
+
current = expression2.getExpression();
|
|
1060
|
+
} else {
|
|
1061
|
+
break;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
if (!baseCall) {
|
|
1065
|
+
return;
|
|
1066
|
+
}
|
|
1067
|
+
const expression = baseCall.getExpression();
|
|
1068
|
+
let typeName = "";
|
|
1069
|
+
if (import_ts_morph.Node.isPropertyAccessExpression(expression)) {
|
|
1070
|
+
typeName = expression.getName();
|
|
1071
|
+
} else {
|
|
1072
|
+
typeName = expression.getText();
|
|
1073
|
+
}
|
|
1074
|
+
fieldInfo.type = typeName;
|
|
1075
|
+
const args = baseCall.getArguments();
|
|
1076
|
+
if (args.length > 0) {
|
|
1077
|
+
const firstArg = args[0];
|
|
1078
|
+
if (import_ts_morph.Node.isStringLiteral(firstArg)) {
|
|
1079
|
+
fieldInfo.columnName = firstArg.getLiteralText();
|
|
1080
|
+
} else if (import_ts_morph.Node.isObjectLiteralExpression(firstArg)) {
|
|
1081
|
+
this.parseTypeConfig(firstArg, fieldInfo);
|
|
1082
|
+
} else if (import_ts_morph.Node.isArrayLiteralExpression(firstArg)) {
|
|
1083
|
+
fieldInfo.enumValues = firstArg.getElements().map((el) => el.getText().replace(/['"]/g, ""));
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
if (args.length > 1 && import_ts_morph.Node.isObjectLiteralExpression(args[1])) {
|
|
1087
|
+
this.parseTypeConfig(args[1], fieldInfo);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
parseTypeConfig(objLiteral, fieldInfo) {
|
|
1091
|
+
if (!import_ts_morph.Node.isObjectLiteralExpression(objLiteral)) {
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
const properties = objLiteral.getProperties();
|
|
1095
|
+
for (const prop of properties) {
|
|
1096
|
+
if (import_ts_morph.Node.isPropertyAssignment(prop)) {
|
|
1097
|
+
const propName = prop.getName();
|
|
1098
|
+
const value = prop.getInitializer()?.getText();
|
|
1099
|
+
switch (propName) {
|
|
1100
|
+
case "length":
|
|
1101
|
+
fieldInfo.length = value ? parseInt(value) : void 0;
|
|
1102
|
+
break;
|
|
1103
|
+
case "precision":
|
|
1104
|
+
fieldInfo.precision = value ? parseInt(value) : void 0;
|
|
1105
|
+
break;
|
|
1106
|
+
case "scale":
|
|
1107
|
+
fieldInfo.scale = value ? parseInt(value) : void 0;
|
|
1108
|
+
break;
|
|
1109
|
+
case "default":
|
|
1110
|
+
fieldInfo.hasDefault = true;
|
|
1111
|
+
fieldInfo.defaultValue = value;
|
|
1112
|
+
break;
|
|
1113
|
+
// 时间精度(用于 timestamp, time 等)
|
|
1114
|
+
case "withTimezone":
|
|
1115
|
+
fieldInfo.withTimezone = value === "true";
|
|
1116
|
+
break;
|
|
1117
|
+
case "mode":
|
|
1118
|
+
fieldInfo.mode = value?.replace(/['"]/g, "");
|
|
1119
|
+
break;
|
|
1120
|
+
default:
|
|
1121
|
+
throw new Error(`Unsupported property: ${propName}`);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
parseCallChain(callExpr, fieldInfo) {
|
|
1127
|
+
let current = callExpr;
|
|
1128
|
+
while (import_ts_morph.Node.isCallExpression(current)) {
|
|
1129
|
+
const expression = current.getExpression();
|
|
1130
|
+
if (import_ts_morph.Node.isPropertyAccessExpression(expression)) {
|
|
1131
|
+
const methodName = expression.getName();
|
|
1132
|
+
const args = current.getArguments();
|
|
1133
|
+
switch (methodName) {
|
|
1134
|
+
case "notNull":
|
|
1135
|
+
fieldInfo.notNull = true;
|
|
1136
|
+
fieldInfo.nullable = false;
|
|
1137
|
+
break;
|
|
1138
|
+
case "default":
|
|
1139
|
+
fieldInfo.hasDefault = true;
|
|
1140
|
+
if (args.length > 0) {
|
|
1141
|
+
fieldInfo.defaultValue = args[0].getText();
|
|
1142
|
+
}
|
|
1143
|
+
break;
|
|
1144
|
+
case "defaultRandom":
|
|
1145
|
+
fieldInfo.hasDefault = true;
|
|
1146
|
+
fieldInfo.defaultValue = "random";
|
|
1147
|
+
break;
|
|
1148
|
+
case "primaryKey":
|
|
1149
|
+
fieldInfo.isPrimaryKey = true;
|
|
1150
|
+
fieldInfo.notNull = true;
|
|
1151
|
+
fieldInfo.nullable = false;
|
|
1152
|
+
break;
|
|
1153
|
+
case "unique":
|
|
1154
|
+
fieldInfo.isUnique = true;
|
|
1155
|
+
break;
|
|
1156
|
+
case "array":
|
|
1157
|
+
fieldInfo.isArray = true;
|
|
1158
|
+
break;
|
|
1159
|
+
case "references":
|
|
1160
|
+
if (args.length > 0) {
|
|
1161
|
+
const refArg = args[0].getText();
|
|
1162
|
+
const match = refArg.match(/=>\s*(\w+)\.(\w+)/);
|
|
1163
|
+
if (match) {
|
|
1164
|
+
fieldInfo.references = {
|
|
1165
|
+
table: match[1],
|
|
1166
|
+
column: match[2]
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
break;
|
|
1171
|
+
default:
|
|
1172
|
+
throw new Error(`Unsupported method: ${methodName}`);
|
|
1173
|
+
}
|
|
1174
|
+
current = expression.getExpression();
|
|
1175
|
+
} else {
|
|
1176
|
+
break;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
};
|
|
1181
|
+
|
|
1182
|
+
// src/helpers/gen-nest-resource/index.ts
|
|
1183
|
+
var import_path = require("path");
|
|
1184
|
+
var import_promises = require("fs/promises");
|
|
1185
|
+
var import_fs = require("fs");
|
|
1186
|
+
async function parseAndGenerateNestResourceTemplate(options) {
|
|
1187
|
+
const parser = new DrizzleSchemaParser({
|
|
1188
|
+
tsConfigFilePath: options.tsConfigFilePath
|
|
1189
|
+
});
|
|
1190
|
+
const tables = parser.parseSchemaFile(options.schemaFilePath);
|
|
1191
|
+
for (const table of tables) {
|
|
1192
|
+
console.info(`\u751F\u6210 Nest.js ${table.variableName} \u6A21\u5757`);
|
|
1193
|
+
const routePath = toKebabCase(table.variableName);
|
|
1194
|
+
const moduleDir = (0, import_path.join)(options.moduleOutputDir, routePath);
|
|
1195
|
+
if ((0, import_fs.existsSync)(moduleDir)) {
|
|
1196
|
+
console.info(`Nest.js \u6A21\u5757 ${routePath} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u751F\u6210\u4EE3\u7801`);
|
|
1197
|
+
continue;
|
|
1198
|
+
}
|
|
1199
|
+
const dto = generateDTO(table);
|
|
1200
|
+
const controller = generateController(table);
|
|
1201
|
+
const service = generateService(table);
|
|
1202
|
+
const moduleFilePath = (0, import_path.join)(moduleDir, `${routePath}.module.ts`);
|
|
1203
|
+
const module2 = generateModule(table);
|
|
1204
|
+
try {
|
|
1205
|
+
await (0, import_promises.mkdir)(moduleDir, {
|
|
1206
|
+
recursive: true
|
|
1207
|
+
});
|
|
1208
|
+
await (0, import_promises.mkdir)((0, import_path.join)(moduleDir, "dtos"), {
|
|
1209
|
+
recursive: true
|
|
1210
|
+
});
|
|
1211
|
+
await (0, import_promises.writeFile)((0, import_path.join)(moduleDir, "dtos", `${routePath}.dto.ts`), dto);
|
|
1212
|
+
await (0, import_promises.writeFile)((0, import_path.join)(moduleDir, `${routePath}.controller.ts`), controller);
|
|
1213
|
+
await (0, import_promises.writeFile)((0, import_path.join)(moduleDir, `${routePath}.service.ts`), service);
|
|
1214
|
+
await (0, import_promises.writeFile)(moduleFilePath, module2);
|
|
1215
|
+
} catch (err) {
|
|
1216
|
+
console.error(`\u751F\u6210 Nest.js ${routePath} \u6A21\u5757\u5931\u8D25: ${err.message}`);
|
|
1217
|
+
await (0, import_promises.rm)(moduleDir, {
|
|
1218
|
+
recursive: true
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
__name(parseAndGenerateNestResourceTemplate, "parseAndGenerateNestResourceTemplate");
|
|
1224
|
+
|
|
557
1225
|
// src/helpers/proxy-error/index.ts
|
|
558
1226
|
var import_node_fs3 = __toESM(require("fs"), 1);
|
|
559
1227
|
var import_node_path3 = __toESM(require("path"), 1);
|
|
@@ -813,7 +1481,7 @@ var import_node_path9 = __toESM(require("path"), 1);
|
|
|
813
1481
|
var import_express = __toESM(require("express"), 1);
|
|
814
1482
|
|
|
815
1483
|
// src/middlewares/openapi/controller.ts
|
|
816
|
-
var
|
|
1484
|
+
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
817
1485
|
var import_node_crypto = __toESM(require("crypto"), 1);
|
|
818
1486
|
|
|
819
1487
|
// src/middlewares/openapi/services.ts
|
|
@@ -925,7 +1593,7 @@ async function enhanceOpenApiWithSourceInfo(options = {}) {
|
|
|
925
1593
|
const startTime = Date.now();
|
|
926
1594
|
const openapiPath = options.openapiPath || import_node_path5.default.resolve(__dirname, "../client/src/api/gen/openapi.json");
|
|
927
1595
|
const serverDir = options.serverDir || import_node_path5.default.resolve(__dirname, "../server");
|
|
928
|
-
const
|
|
1596
|
+
const writeFile2 = options.writeFile !== false;
|
|
929
1597
|
let openapi;
|
|
930
1598
|
if (options.openapiData) {
|
|
931
1599
|
openapi = JSON.parse(JSON.stringify(options.openapiData));
|
|
@@ -936,7 +1604,7 @@ async function enhanceOpenApiWithSourceInfo(options = {}) {
|
|
|
936
1604
|
const controllerFiles = await findControllerFiles(serverDir);
|
|
937
1605
|
const sourceMap = await buildSourceMap(controllerFiles, processControllerFile);
|
|
938
1606
|
const enhanced = enhanceOpenApiPaths(openapi, sourceMap);
|
|
939
|
-
if (
|
|
1607
|
+
if (writeFile2) {
|
|
940
1608
|
await import_node_fs5.promises.writeFile(openapiPath, JSON.stringify(openapi, null, 2) + "\n", "utf-8");
|
|
941
1609
|
}
|
|
942
1610
|
const duration = Date.now() - startTime;
|
|
@@ -1046,7 +1714,7 @@ function createOpenapiHandler(openapiFilePath, enableEnhancement, serverDir) {
|
|
|
1046
1714
|
let cache = null;
|
|
1047
1715
|
return async (_req, res, context) => {
|
|
1048
1716
|
try {
|
|
1049
|
-
const fileBuffer = await
|
|
1717
|
+
const fileBuffer = await import_promises2.default.readFile(openapiFilePath, "utf-8");
|
|
1050
1718
|
const currentHash = import_node_crypto.default.createHash("md5").update(fileBuffer).digest("hex");
|
|
1051
1719
|
if (cache && cache.fileHash === currentHash) {
|
|
1052
1720
|
return res.json(cache.data);
|
|
@@ -1674,8 +2342,8 @@ __name(createDevLogsMiddleware, "createDevLogsMiddleware");
|
|
|
1674
2342
|
var import_express3 = __toESM(require("express"), 1);
|
|
1675
2343
|
|
|
1676
2344
|
// src/middlewares/collect-logs/controller.ts
|
|
1677
|
-
var
|
|
1678
|
-
var
|
|
2345
|
+
var import_path2 = require("path");
|
|
2346
|
+
var import_fs2 = __toESM(require("fs"), 1);
|
|
1679
2347
|
|
|
1680
2348
|
// src/middlewares/collect-logs/utils.ts
|
|
1681
2349
|
var import_node_path8 = require("path");
|
|
@@ -1707,7 +2375,7 @@ __name(serializeError2, "serializeError");
|
|
|
1707
2375
|
|
|
1708
2376
|
// src/middlewares/collect-logs/controller.ts
|
|
1709
2377
|
function collectLogsHandler(logDir, fileName) {
|
|
1710
|
-
const filePath = (0,
|
|
2378
|
+
const filePath = (0, import_path2.join)(logDir, fileName);
|
|
1711
2379
|
ensureDir(logDir);
|
|
1712
2380
|
return async (req, res) => {
|
|
1713
2381
|
try {
|
|
@@ -1721,7 +2389,7 @@ function collectLogsHandler(logDir, fileName) {
|
|
|
1721
2389
|
...logContent,
|
|
1722
2390
|
server_time: (/* @__PURE__ */ new Date()).toISOString()
|
|
1723
2391
|
}) + "\n";
|
|
1724
|
-
await
|
|
2392
|
+
await import_fs2.default.promises.appendFile(filePath, logLine);
|
|
1725
2393
|
res.json({
|
|
1726
2394
|
success: true
|
|
1727
2395
|
});
|
|
@@ -1732,7 +2400,7 @@ function collectLogsHandler(logDir, fileName) {
|
|
|
1732
2400
|
}
|
|
1733
2401
|
__name(collectLogsHandler, "collectLogsHandler");
|
|
1734
2402
|
function collectLogsBatchHandler(logDir, fileName) {
|
|
1735
|
-
const filePath = (0,
|
|
2403
|
+
const filePath = (0, import_path2.join)(logDir, fileName);
|
|
1736
2404
|
ensureDir(logDir);
|
|
1737
2405
|
return async (req, res) => {
|
|
1738
2406
|
try {
|
|
@@ -1749,7 +2417,7 @@ function collectLogsBatchHandler(logDir, fileName) {
|
|
|
1749
2417
|
server_time: (/* @__PURE__ */ new Date()).toISOString()
|
|
1750
2418
|
}) + "\n");
|
|
1751
2419
|
}
|
|
1752
|
-
await
|
|
2420
|
+
await import_fs2.default.promises.appendFile(filePath, logLines.join(""));
|
|
1753
2421
|
res.json({
|
|
1754
2422
|
success: true
|
|
1755
2423
|
});
|
|
@@ -1888,6 +2556,7 @@ __name(registerMiddlewares, "registerMiddlewares");
|
|
|
1888
2556
|
createOpenapiMiddleware,
|
|
1889
2557
|
handleDevProxyError,
|
|
1890
2558
|
normalizeBasePath,
|
|
2559
|
+
parseAndGenerateNestResourceTemplate,
|
|
1891
2560
|
postprocessDrizzleSchema,
|
|
1892
2561
|
registerMiddlewares
|
|
1893
2562
|
});
|