@lark-apaas/devtool-kits 1.1.1-alpha.0 → 1.2.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/error.html +194 -23
- package/dist/index.cjs +691 -15
- 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 +695 -20
- 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);
|
|
@@ -729,10 +1397,17 @@ function checkForErrors(logs) {
|
|
|
729
1397
|
return false;
|
|
730
1398
|
}
|
|
731
1399
|
__name(checkForErrors, "checkForErrors");
|
|
732
|
-
function
|
|
733
|
-
|
|
1400
|
+
function injectErrorData(template, errorLogs, clientBasePath) {
|
|
1401
|
+
let logsText = "";
|
|
1402
|
+
if (errorLogs.length > 0) {
|
|
1403
|
+
logsText = errorLogs.join("\n");
|
|
1404
|
+
} else {
|
|
1405
|
+
logsText = "\u672A\u627E\u5230\u76F8\u5173\u9519\u8BEF\u65E5\u5FD7";
|
|
1406
|
+
}
|
|
1407
|
+
return template.replace("{{.errorData.message}}", `\u670D\u52A1\u542F\u52A8\u5F02\u5E38\uFF0C\u8BF7\u6839\u636E\u65E5\u5FD7\u4FEE\u590D\u76F8\u5173\u95EE\u9898
|
|
1408
|
+
${JSON.stringify(logsText)}`).replace("{{.clientBasePath}}", clientBasePath);
|
|
734
1409
|
}
|
|
735
|
-
__name(
|
|
1410
|
+
__name(injectErrorData, "injectErrorData");
|
|
736
1411
|
function handleDevProxyError(err, req, res, options) {
|
|
737
1412
|
const { logDir = import_node_path3.default.join(process.cwd(), "logs"), maxErrorLogs = 100, logFileName = "server.log", retryTimeout = 5e3, retryInterval = 500, target = `http://localhost:${process.env.SERVER_PORT || 3e3}`, clientBasePath = process.env.CLIENT_BASE_PATH || "/" } = options || {};
|
|
738
1413
|
const clientBasePathWithoutSlash = normalizeBasePath(clientBasePath);
|
|
@@ -744,7 +1419,7 @@ function handleDevProxyError(err, req, res, options) {
|
|
|
744
1419
|
(async () => {
|
|
745
1420
|
try {
|
|
746
1421
|
const isConnError = isConnectionError(err);
|
|
747
|
-
const { hasCompileError } = await readRecentErrorLogs(logDir, maxErrorLogs, logFileName);
|
|
1422
|
+
const { logs: errorLogs, hasCompileError } = await readRecentErrorLogs(logDir, maxErrorLogs, logFileName);
|
|
748
1423
|
if (isConnError && !hasCompileError) {
|
|
749
1424
|
console.log("[Proxy Error]: Connection error without compile errors, possibly server restarting...");
|
|
750
1425
|
try {
|
|
@@ -768,7 +1443,7 @@ function handleDevProxyError(err, req, res, options) {
|
|
|
768
1443
|
console.log("[Proxy Error]: Compile error or non-connection error, showing error page");
|
|
769
1444
|
}
|
|
770
1445
|
const template = getErrorHtmlTemplate();
|
|
771
|
-
const html =
|
|
1446
|
+
const html = injectErrorData(template, errorLogs, clientBasePathWithoutSlash);
|
|
772
1447
|
res.writeHead(200, {
|
|
773
1448
|
"Content-Type": "text/html; charset=utf-8",
|
|
774
1449
|
"Cache-Control": "no-cache, no-store, must-revalidate",
|
|
@@ -806,7 +1481,7 @@ var import_node_path9 = __toESM(require("path"), 1);
|
|
|
806
1481
|
var import_express = __toESM(require("express"), 1);
|
|
807
1482
|
|
|
808
1483
|
// src/middlewares/openapi/controller.ts
|
|
809
|
-
var
|
|
1484
|
+
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
810
1485
|
var import_node_crypto = __toESM(require("crypto"), 1);
|
|
811
1486
|
|
|
812
1487
|
// src/middlewares/openapi/services.ts
|
|
@@ -918,7 +1593,7 @@ async function enhanceOpenApiWithSourceInfo(options = {}) {
|
|
|
918
1593
|
const startTime = Date.now();
|
|
919
1594
|
const openapiPath = options.openapiPath || import_node_path5.default.resolve(__dirname, "../client/src/api/gen/openapi.json");
|
|
920
1595
|
const serverDir = options.serverDir || import_node_path5.default.resolve(__dirname, "../server");
|
|
921
|
-
const
|
|
1596
|
+
const writeFile2 = options.writeFile !== false;
|
|
922
1597
|
let openapi;
|
|
923
1598
|
if (options.openapiData) {
|
|
924
1599
|
openapi = JSON.parse(JSON.stringify(options.openapiData));
|
|
@@ -929,7 +1604,7 @@ async function enhanceOpenApiWithSourceInfo(options = {}) {
|
|
|
929
1604
|
const controllerFiles = await findControllerFiles(serverDir);
|
|
930
1605
|
const sourceMap = await buildSourceMap(controllerFiles, processControllerFile);
|
|
931
1606
|
const enhanced = enhanceOpenApiPaths(openapi, sourceMap);
|
|
932
|
-
if (
|
|
1607
|
+
if (writeFile2) {
|
|
933
1608
|
await import_node_fs5.promises.writeFile(openapiPath, JSON.stringify(openapi, null, 2) + "\n", "utf-8");
|
|
934
1609
|
}
|
|
935
1610
|
const duration = Date.now() - startTime;
|
|
@@ -1039,7 +1714,7 @@ function createOpenapiHandler(openapiFilePath, enableEnhancement, serverDir) {
|
|
|
1039
1714
|
let cache = null;
|
|
1040
1715
|
return async (_req, res, context) => {
|
|
1041
1716
|
try {
|
|
1042
|
-
const fileBuffer = await
|
|
1717
|
+
const fileBuffer = await import_promises2.default.readFile(openapiFilePath, "utf-8");
|
|
1043
1718
|
const currentHash = import_node_crypto.default.createHash("md5").update(fileBuffer).digest("hex");
|
|
1044
1719
|
if (cache && cache.fileHash === currentHash) {
|
|
1045
1720
|
return res.json(cache.data);
|
|
@@ -1667,8 +2342,8 @@ __name(createDevLogsMiddleware, "createDevLogsMiddleware");
|
|
|
1667
2342
|
var import_express3 = __toESM(require("express"), 1);
|
|
1668
2343
|
|
|
1669
2344
|
// src/middlewares/collect-logs/controller.ts
|
|
1670
|
-
var
|
|
1671
|
-
var
|
|
2345
|
+
var import_path2 = require("path");
|
|
2346
|
+
var import_fs2 = __toESM(require("fs"), 1);
|
|
1672
2347
|
|
|
1673
2348
|
// src/middlewares/collect-logs/utils.ts
|
|
1674
2349
|
var import_node_path8 = require("path");
|
|
@@ -1700,7 +2375,7 @@ __name(serializeError2, "serializeError");
|
|
|
1700
2375
|
|
|
1701
2376
|
// src/middlewares/collect-logs/controller.ts
|
|
1702
2377
|
function collectLogsHandler(logDir, fileName) {
|
|
1703
|
-
const filePath = (0,
|
|
2378
|
+
const filePath = (0, import_path2.join)(logDir, fileName);
|
|
1704
2379
|
ensureDir(logDir);
|
|
1705
2380
|
return async (req, res) => {
|
|
1706
2381
|
try {
|
|
@@ -1714,7 +2389,7 @@ function collectLogsHandler(logDir, fileName) {
|
|
|
1714
2389
|
...logContent,
|
|
1715
2390
|
server_time: (/* @__PURE__ */ new Date()).toISOString()
|
|
1716
2391
|
}) + "\n";
|
|
1717
|
-
await
|
|
2392
|
+
await import_fs2.default.promises.appendFile(filePath, logLine);
|
|
1718
2393
|
res.json({
|
|
1719
2394
|
success: true
|
|
1720
2395
|
});
|
|
@@ -1725,7 +2400,7 @@ function collectLogsHandler(logDir, fileName) {
|
|
|
1725
2400
|
}
|
|
1726
2401
|
__name(collectLogsHandler, "collectLogsHandler");
|
|
1727
2402
|
function collectLogsBatchHandler(logDir, fileName) {
|
|
1728
|
-
const filePath = (0,
|
|
2403
|
+
const filePath = (0, import_path2.join)(logDir, fileName);
|
|
1729
2404
|
ensureDir(logDir);
|
|
1730
2405
|
return async (req, res) => {
|
|
1731
2406
|
try {
|
|
@@ -1742,7 +2417,7 @@ function collectLogsBatchHandler(logDir, fileName) {
|
|
|
1742
2417
|
server_time: (/* @__PURE__ */ new Date()).toISOString()
|
|
1743
2418
|
}) + "\n");
|
|
1744
2419
|
}
|
|
1745
|
-
await
|
|
2420
|
+
await import_fs2.default.promises.appendFile(filePath, logLines.join(""));
|
|
1746
2421
|
res.json({
|
|
1747
2422
|
success: true
|
|
1748
2423
|
});
|
|
@@ -1881,6 +2556,7 @@ __name(registerMiddlewares, "registerMiddlewares");
|
|
|
1881
2556
|
createOpenapiMiddleware,
|
|
1882
2557
|
handleDevProxyError,
|
|
1883
2558
|
normalizeBasePath,
|
|
2559
|
+
parseAndGenerateNestResourceTemplate,
|
|
1884
2560
|
postprocessDrizzleSchema,
|
|
1885
2561
|
registerMiddlewares
|
|
1886
2562
|
});
|