@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.js
CHANGED
|
@@ -514,6 +514,673 @@ function postprocessDrizzleSchema(targetPath) {
|
|
|
514
514
|
}
|
|
515
515
|
__name(postprocessDrizzleSchema, "postprocessDrizzleSchema");
|
|
516
516
|
|
|
517
|
+
// src/helpers/gen-nest-resource/utils.ts
|
|
518
|
+
function mapDrizzleTypeToTS(field) {
|
|
519
|
+
const typeMap = {
|
|
520
|
+
// String types
|
|
521
|
+
char: "string",
|
|
522
|
+
varchar: "string",
|
|
523
|
+
text: "string",
|
|
524
|
+
// Numeric types
|
|
525
|
+
smallint: "number",
|
|
526
|
+
integer: "number",
|
|
527
|
+
int: "number",
|
|
528
|
+
bigint: "string",
|
|
529
|
+
serial: "number",
|
|
530
|
+
smallserial: "number",
|
|
531
|
+
bigserial: "string",
|
|
532
|
+
// Decimal types
|
|
533
|
+
decimal: "string",
|
|
534
|
+
numeric: "string",
|
|
535
|
+
real: "number",
|
|
536
|
+
doublePrecision: "number",
|
|
537
|
+
// Boolean
|
|
538
|
+
boolean: "boolean",
|
|
539
|
+
// Date/Time types
|
|
540
|
+
timestamp: "Date",
|
|
541
|
+
timestamptz: "Date",
|
|
542
|
+
date: "Date",
|
|
543
|
+
time: "string",
|
|
544
|
+
timetz: "string",
|
|
545
|
+
interval: "string",
|
|
546
|
+
// UUID
|
|
547
|
+
uuid: "string",
|
|
548
|
+
// JSON types
|
|
549
|
+
json: "any",
|
|
550
|
+
jsonb: "any",
|
|
551
|
+
// Binary
|
|
552
|
+
bytea: "Buffer",
|
|
553
|
+
// Network types
|
|
554
|
+
inet: "string",
|
|
555
|
+
cidr: "string",
|
|
556
|
+
macaddr: "string",
|
|
557
|
+
macaddr8: "string",
|
|
558
|
+
// Geometric types
|
|
559
|
+
point: "{ x: number; y: number }",
|
|
560
|
+
line: "string",
|
|
561
|
+
lseg: "string",
|
|
562
|
+
box: "string",
|
|
563
|
+
path: "string",
|
|
564
|
+
polygon: "string",
|
|
565
|
+
circle: "string",
|
|
566
|
+
// Array types (handled by isArray flag)
|
|
567
|
+
array: "any[]",
|
|
568
|
+
// Custom types
|
|
569
|
+
customType: "any",
|
|
570
|
+
customTimestamptz: "Date",
|
|
571
|
+
userProfile: "string",
|
|
572
|
+
fileAttachment: "FileAttachment",
|
|
573
|
+
// Enum (handled separately)
|
|
574
|
+
pgEnum: "string"
|
|
575
|
+
};
|
|
576
|
+
let baseType = typeMap[field.type] || "any";
|
|
577
|
+
if (field.isArray) {
|
|
578
|
+
baseType = baseType.endsWith("[]") ? baseType : `${baseType}[]`;
|
|
579
|
+
}
|
|
580
|
+
if (field.enumValues && field.enumValues.length > 0) {
|
|
581
|
+
baseType = field.enumValues.map((v) => `'${v}'`).join(" | ");
|
|
582
|
+
}
|
|
583
|
+
return baseType;
|
|
584
|
+
}
|
|
585
|
+
__name(mapDrizzleTypeToTS, "mapDrizzleTypeToTS");
|
|
586
|
+
function toPascalCase(str) {
|
|
587
|
+
return str.replace(/([a-z])([A-Z])/g, "$1 $2").split(/[-_\s]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
588
|
+
}
|
|
589
|
+
__name(toPascalCase, "toPascalCase");
|
|
590
|
+
function toKebabCase(str) {
|
|
591
|
+
return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase().replace(/[_\s]/g, "-");
|
|
592
|
+
}
|
|
593
|
+
__name(toKebabCase, "toKebabCase");
|
|
594
|
+
|
|
595
|
+
// src/helpers/gen-nest-resource/generator.ts
|
|
596
|
+
function generateDTO(table) {
|
|
597
|
+
const className = toPascalCase(table.variableName);
|
|
598
|
+
let dto = `// \u8BF7\u4FEE\u6539\u8BE5\u6587\u4EF6\u4EE3\u7801\u4EE5\u6EE1\u8DB3\u9700\u6C42
|
|
599
|
+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
600
|
+
import { IsDefined, IsNumber, IsOptional, IsString, MaxLength, IsInt, IsBoolean, IsUUID, IsDate, IsObject, IsArray } from 'class-validator';
|
|
601
|
+
import { Type } from 'class-transformer';
|
|
602
|
+
import { FileAttachment } from '../../../database/schema';
|
|
603
|
+
|
|
604
|
+
`;
|
|
605
|
+
dto += `export class Create${className}Dto {
|
|
606
|
+
`;
|
|
607
|
+
for (const field of table.fields) {
|
|
608
|
+
if (field.isPrimaryKey || field.name === "id" || field.name.startsWith("_") || field.name.startsWith("created") || field.name.startsWith("updated")) {
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
const tsType = mapDrizzleTypeToTS(field);
|
|
612
|
+
const optional = field.nullable || field.hasDefault ? "?" : "";
|
|
613
|
+
const decorators = generateValidationDecorators(field);
|
|
614
|
+
if (decorators) {
|
|
615
|
+
dto += decorators;
|
|
616
|
+
}
|
|
617
|
+
dto += ` ${field.name}${optional}: ${tsType};
|
|
618
|
+
|
|
619
|
+
`;
|
|
620
|
+
}
|
|
621
|
+
dto += "}\n\n";
|
|
622
|
+
dto += `export class Update${className}Dto {
|
|
623
|
+
`;
|
|
624
|
+
for (const field of table.fields) {
|
|
625
|
+
if (field.name.startsWith("_") || field.name.startsWith("created") || field.name.startsWith("updated") || field.isPrimaryKey || field.name === "id") {
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
const tsType = mapDrizzleTypeToTS(field);
|
|
629
|
+
const decorators = generateValidationDecorators(field, {
|
|
630
|
+
isUpdate: true
|
|
631
|
+
});
|
|
632
|
+
if (decorators) {
|
|
633
|
+
dto += decorators;
|
|
634
|
+
}
|
|
635
|
+
dto += ` ${field.name}?: ${tsType};
|
|
636
|
+
|
|
637
|
+
`;
|
|
638
|
+
}
|
|
639
|
+
dto += "}\n\n";
|
|
640
|
+
dto += `export class ${className}ResponseDto {
|
|
641
|
+
`;
|
|
642
|
+
for (const field of table.fields) {
|
|
643
|
+
const tsType = mapDrizzleTypeToTS(field);
|
|
644
|
+
const optional = field.nullable ? "?" : "";
|
|
645
|
+
const decorators = generateValidationDecorators(field, {
|
|
646
|
+
isResponse: true
|
|
647
|
+
});
|
|
648
|
+
if (decorators) {
|
|
649
|
+
dto += decorators;
|
|
650
|
+
}
|
|
651
|
+
dto += ` ${field.name}${optional}: ${tsType};
|
|
652
|
+
|
|
653
|
+
`;
|
|
654
|
+
}
|
|
655
|
+
dto += "}\n";
|
|
656
|
+
return dto;
|
|
657
|
+
}
|
|
658
|
+
__name(generateDTO, "generateDTO");
|
|
659
|
+
function generateValidationDecorators(field, { isUpdate = false, isResponse = false } = {}) {
|
|
660
|
+
let decorators = " // \u8BF7\u6309\u7528\u6237\u9700\u6C42\u4FEE\u6539\u4EE5\u4E0B\u88C5\u9970\u5668\u6CE8\u91CA\n";
|
|
661
|
+
if (field.nullable || !isResponse && field.hasDefault || isUpdate) {
|
|
662
|
+
decorators += ` @ApiPropertyOptional({ description: '${field.comment || field.name}' })
|
|
663
|
+
`;
|
|
664
|
+
if (isResponse) {
|
|
665
|
+
return decorators;
|
|
666
|
+
}
|
|
667
|
+
decorators += " @IsOptional()\n";
|
|
668
|
+
} else {
|
|
669
|
+
decorators += ` @ApiProperty({ description: '${field.comment || field.name}' })
|
|
670
|
+
`;
|
|
671
|
+
if (isResponse) {
|
|
672
|
+
return decorators;
|
|
673
|
+
}
|
|
674
|
+
decorators += " @IsDefined()\n";
|
|
675
|
+
}
|
|
676
|
+
switch (field.type) {
|
|
677
|
+
case "varchar":
|
|
678
|
+
case "char":
|
|
679
|
+
case "text":
|
|
680
|
+
decorators += " @IsString()\n";
|
|
681
|
+
if (field.length) {
|
|
682
|
+
decorators += ` @MaxLength(${field.length})
|
|
683
|
+
`;
|
|
684
|
+
}
|
|
685
|
+
break;
|
|
686
|
+
case "integer":
|
|
687
|
+
case "smallint":
|
|
688
|
+
case "serial":
|
|
689
|
+
case "smallserial":
|
|
690
|
+
decorators += " @IsInt()\n";
|
|
691
|
+
break;
|
|
692
|
+
case "decimal":
|
|
693
|
+
case "numeric":
|
|
694
|
+
case "real":
|
|
695
|
+
case "doublePrecision":
|
|
696
|
+
decorators += " @IsNumber()\n";
|
|
697
|
+
break;
|
|
698
|
+
case "boolean":
|
|
699
|
+
decorators += " @IsBoolean()\n";
|
|
700
|
+
break;
|
|
701
|
+
case "uuid":
|
|
702
|
+
decorators += " @IsUUID()\n";
|
|
703
|
+
break;
|
|
704
|
+
case "timestamp":
|
|
705
|
+
case "timestamptz":
|
|
706
|
+
case "date":
|
|
707
|
+
case "customTimestamptz":
|
|
708
|
+
decorators += " @IsDate()\n";
|
|
709
|
+
break;
|
|
710
|
+
case "json":
|
|
711
|
+
case "jsonb":
|
|
712
|
+
decorators += " @IsObject()\n";
|
|
713
|
+
break;
|
|
714
|
+
}
|
|
715
|
+
if (field.isArray) {
|
|
716
|
+
decorators += " @IsArray()\n";
|
|
717
|
+
}
|
|
718
|
+
return decorators;
|
|
719
|
+
}
|
|
720
|
+
__name(generateValidationDecorators, "generateValidationDecorators");
|
|
721
|
+
function generateController(table) {
|
|
722
|
+
const className = toPascalCase(table.variableName);
|
|
723
|
+
const routePath = toKebabCase(table.variableName);
|
|
724
|
+
const pkField = table.fields.find((f) => f.isPrimaryKey);
|
|
725
|
+
const pkType = pkField ? mapDrizzleTypeToTS(pkField) : "string";
|
|
726
|
+
const pkName = pkField ? pkField.name : "id";
|
|
727
|
+
const controller = `
|
|
728
|
+
// \u8BF7\u4FEE\u6539\u8BE5\u6587\u4EF6\u4EE3\u7801\u4EE5\u6EE1\u8DB3\u9700\u6C42
|
|
729
|
+
import {
|
|
730
|
+
Controller,
|
|
731
|
+
Get,
|
|
732
|
+
Post,
|
|
733
|
+
Put,
|
|
734
|
+
Delete,
|
|
735
|
+
Body,
|
|
736
|
+
Param,
|
|
737
|
+
Query,
|
|
738
|
+
} from '@nestjs/common';
|
|
739
|
+
import {
|
|
740
|
+
ApiTags,
|
|
741
|
+
ApiOperation,
|
|
742
|
+
ApiOkResponse,
|
|
743
|
+
ApiCreatedResponse,
|
|
744
|
+
} from '@nestjs/swagger';
|
|
745
|
+
import {
|
|
746
|
+
Create${className}Dto,
|
|
747
|
+
Update${className}Dto,
|
|
748
|
+
${className}ResponseDto
|
|
749
|
+
} from './dtos/${routePath}.dto';
|
|
750
|
+
import { ${className}Service } from './${routePath}.service';
|
|
751
|
+
|
|
752
|
+
@ApiTags('${toPascalCase(table.variableName)}')
|
|
753
|
+
@Controller('api/${routePath}')
|
|
754
|
+
export class ${className}Controller {
|
|
755
|
+
constructor(private readonly ${table.variableName}Service: ${className}Service) {}
|
|
756
|
+
|
|
757
|
+
@Post()
|
|
758
|
+
@ApiOperation({
|
|
759
|
+
summary: '\u521B\u5EFA\u4E00\u6761\u8BB0\u5F55\uFF08\u6A21\u677F\u5185\u5BB9\uFF0C\u8BF7\u4FEE\u6539\u6211\uFF09',
|
|
760
|
+
description: '\u521B\u5EFA\u4E00\u6761\u8BB0\u5F55\uFF08\u6A21\u677F\u5185\u5BB9\uFF0C\u8BF7\u4FEE\u6539\u6211\uFF09',
|
|
761
|
+
})
|
|
762
|
+
@ApiCreatedResponse({
|
|
763
|
+
description: '\u6210\u529F\u521B\u5EFA\u4E00\u6761\u8BB0\u5F55',
|
|
764
|
+
type: ${className}ResponseDto,
|
|
765
|
+
})
|
|
766
|
+
async create(
|
|
767
|
+
@Body() createDto: Create${className}Dto
|
|
768
|
+
): Promise<${className}ResponseDto> {
|
|
769
|
+
return this.${table.variableName}Service.create(createDto);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
@ApiOperation({
|
|
773
|
+
summary: '\u6839\u636E\u4E3B\u952E\u67E5\u8BE2\u4E00\u6761\u8BB0\u5F55\uFF08\u6A21\u677F\u5185\u5BB9\uFF0C\u8BF7\u4FEE\u6539\u6211\uFF09',
|
|
774
|
+
description: '\u6839\u636E\u4E3B\u952E\u67E5\u8BE2\u4E00\u6761\u8BB0\u5F55\uFF08\u6A21\u677F\u5185\u5BB9\uFF0C\u8BF7\u4FEE\u6539\u6211\uFF09',
|
|
775
|
+
})
|
|
776
|
+
@ApiOkResponse({
|
|
777
|
+
description: '\u6210\u529F\u67E5\u8BE2\u4E00\u6761\u8BB0\u5F55',
|
|
778
|
+
type: ${className}ResponseDto,
|
|
779
|
+
})
|
|
780
|
+
@Get(':${pkName}')
|
|
781
|
+
async findOne(
|
|
782
|
+
@Param('${pkName}') ${pkName}: ${pkType}
|
|
783
|
+
): Promise<${className}ResponseDto> {
|
|
784
|
+
return this.${table.variableName}Service.findOne(${pkName});
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
@ApiOperation({
|
|
788
|
+
summary: '\u6839\u636E\u4E3B\u952E\u66F4\u65B0\u4E00\u6761\u8BB0\u5F55\uFF08\u6A21\u677F\u5185\u5BB9\uFF0C\u8BF7\u4FEE\u6539\u6211\uFF09',
|
|
789
|
+
description: '\u6839\u636E\u4E3B\u952E\u66F4\u65B0\u4E00\u6761\u8BB0\u5F55\uFF08\u6A21\u677F\u5185\u5BB9\uFF0C\u8BF7\u4FEE\u6539\u6211\uFF09',
|
|
790
|
+
})
|
|
791
|
+
@ApiOkResponse({
|
|
792
|
+
description: '\u6210\u529F\u66F4\u65B0\u4E00\u6761\u8BB0\u5F55',
|
|
793
|
+
type: ${className}ResponseDto,
|
|
794
|
+
})
|
|
795
|
+
@Put(':${pkName}')
|
|
796
|
+
async update(
|
|
797
|
+
@Param('${pkName}') ${pkName}: ${pkType},
|
|
798
|
+
@Body() updateDto: Update${className}Dto
|
|
799
|
+
): Promise<${className}ResponseDto> {
|
|
800
|
+
return this.${table.variableName}Service.update(${pkName}, updateDto);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
@ApiOperation({
|
|
804
|
+
summary: '\u6839\u636E\u4E3B\u952E\u5220\u9664\u4E00\u6761\u8BB0\u5F55\uFF08\u6A21\u677F\u5185\u5BB9\uFF0C\u8BF7\u4FEE\u6539\u6211\uFF09',
|
|
805
|
+
description: '\u6839\u636E\u4E3B\u952E\u5220\u9664\u4E00\u6761\u8BB0\u5F55\uFF08\u6A21\u677F\u5185\u5BB9\uFF0C\u8BF7\u4FEE\u6539\u6211\uFF09',
|
|
806
|
+
})
|
|
807
|
+
@ApiOkResponse({
|
|
808
|
+
description: '\u6210\u529F\u5220\u9664\u4E00\u6761\u8BB0\u5F55',
|
|
809
|
+
})
|
|
810
|
+
@Delete(':${pkName}')
|
|
811
|
+
async remove(
|
|
812
|
+
@Param('${pkName}') ${pkName}: ${pkType}
|
|
813
|
+
): Promise<void> {
|
|
814
|
+
return this.${table.variableName}Service.remove(${pkName});
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
`;
|
|
818
|
+
return controller;
|
|
819
|
+
}
|
|
820
|
+
__name(generateController, "generateController");
|
|
821
|
+
function generateService(table) {
|
|
822
|
+
const className = toPascalCase(table.variableName);
|
|
823
|
+
const routePath = toKebabCase(table.variableName);
|
|
824
|
+
const pkField = table.fields.find((f) => f.isPrimaryKey);
|
|
825
|
+
const pkType = pkField ? mapDrizzleTypeToTS(pkField) : "string";
|
|
826
|
+
const pkName = pkField ? pkField.name : "id";
|
|
827
|
+
const service = `
|
|
828
|
+
// \u8BF7\u4FEE\u6539\u8BE5\u6587\u4EF6\u4EE3\u7801\u4EE5\u6EE1\u8DB3\u9700\u6C42
|
|
829
|
+
import { Injectable, Inject, Logger, NotFoundException } from '@nestjs/common';
|
|
830
|
+
import { eq } from 'drizzle-orm';
|
|
831
|
+
import { DRIZZLE_DATABASE, type PostgresJsDatabase } from '@lark-apaas/fullstack-nestjs-core';
|
|
832
|
+
import { ${table.variableName} } from '../../database/schema';
|
|
833
|
+
import {
|
|
834
|
+
Create${className}Dto,
|
|
835
|
+
Update${className}Dto,
|
|
836
|
+
${className}ResponseDto
|
|
837
|
+
} from './dtos/${routePath}.dto';
|
|
838
|
+
|
|
839
|
+
@Injectable()
|
|
840
|
+
export class ${className}Service {
|
|
841
|
+
private readonly logger = new Logger(${className}Service.name);
|
|
842
|
+
|
|
843
|
+
constructor(@Inject(DRIZZLE_DATABASE) private readonly db: PostgresJsDatabase) {}
|
|
844
|
+
|
|
845
|
+
async create(createDto: Create${className}Dto): Promise<${className}ResponseDto> {
|
|
846
|
+
const [result] = await this.db
|
|
847
|
+
.insert(${table.variableName})
|
|
848
|
+
.values(createDto)
|
|
849
|
+
.returning();
|
|
850
|
+
|
|
851
|
+
this.logger.log(\`Created ${className} with ${pkName} \${result.${pkName}}\`);
|
|
852
|
+
|
|
853
|
+
return result;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
async findAll(options?: { page?: number; limit?: number }): Promise<${className}ResponseDto[]> {
|
|
857
|
+
const { page = 1, limit = 10 } = options || {};
|
|
858
|
+
const offset = (page - 1) * limit;
|
|
859
|
+
|
|
860
|
+
return this.db
|
|
861
|
+
.select()
|
|
862
|
+
.from(${table.variableName})
|
|
863
|
+
.limit(limit)
|
|
864
|
+
.offset(offset);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
async findOne(${pkName}: ${pkType}): Promise<${className}ResponseDto> {
|
|
868
|
+
const [result] = await this.db
|
|
869
|
+
.select()
|
|
870
|
+
.from(${table.variableName})
|
|
871
|
+
.where(eq(${table.variableName}.${pkName}, ${pkName}))
|
|
872
|
+
.limit(1);
|
|
873
|
+
|
|
874
|
+
if (!result) {
|
|
875
|
+
throw new NotFoundException(\`${className} with ${pkName} \${${pkName}} not found\`);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
return result;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
async update(${pkName}: ${pkType}, updateDto: Update${className}Dto): Promise<${className}ResponseDto> {
|
|
882
|
+
const [result] = await this.db
|
|
883
|
+
.update(${table.variableName})
|
|
884
|
+
.set(updateDto)
|
|
885
|
+
.where(eq(${table.variableName}.${pkName}, ${pkName}))
|
|
886
|
+
.returning();
|
|
887
|
+
|
|
888
|
+
if (!result) {
|
|
889
|
+
throw new NotFoundException(\`${className} with ${pkName} \${${pkName}} not found\`);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
return result;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
async remove(${pkName}: ${pkType}): Promise<void> {
|
|
896
|
+
const result = await this.db
|
|
897
|
+
.delete(${table.variableName})
|
|
898
|
+
.where(eq(${table.variableName}.${pkName}, ${pkName}))
|
|
899
|
+
.returning();
|
|
900
|
+
|
|
901
|
+
if (result.length === 0) {
|
|
902
|
+
throw new NotFoundException(\`${className} with ${pkName} \${${pkName}} not found\`);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
this.logger.log(\`Deleted ${className} with ${pkName} \${${pkName}}\`);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
`;
|
|
909
|
+
return service;
|
|
910
|
+
}
|
|
911
|
+
__name(generateService, "generateService");
|
|
912
|
+
function generateModule(table) {
|
|
913
|
+
const className = toPascalCase(table.variableName);
|
|
914
|
+
const routePath = toKebabCase(table.variableName);
|
|
915
|
+
const module = `
|
|
916
|
+
import { Module } from '@nestjs/common';
|
|
917
|
+
import { ${className}Controller } from './${routePath}.controller';
|
|
918
|
+
import { ${className}Service } from './${routePath}.service';
|
|
919
|
+
|
|
920
|
+
@Module({
|
|
921
|
+
controllers: [${className}Controller],
|
|
922
|
+
providers: [${className}Service],
|
|
923
|
+
})
|
|
924
|
+
export class ${className}Module {}
|
|
925
|
+
`;
|
|
926
|
+
return module;
|
|
927
|
+
}
|
|
928
|
+
__name(generateModule, "generateModule");
|
|
929
|
+
|
|
930
|
+
// src/helpers/gen-nest-resource/schema-parser.ts
|
|
931
|
+
import { Project, Node } from "ts-morph";
|
|
932
|
+
var DrizzleSchemaParser = class DrizzleSchemaParser2 {
|
|
933
|
+
static {
|
|
934
|
+
__name(this, "DrizzleSchemaParser");
|
|
935
|
+
}
|
|
936
|
+
project;
|
|
937
|
+
constructor(projectOptions) {
|
|
938
|
+
this.project = new Project(projectOptions);
|
|
939
|
+
}
|
|
940
|
+
parseSchemaFile(filePath) {
|
|
941
|
+
const sourceFile = this.project.addSourceFileAtPath(filePath);
|
|
942
|
+
const tables = [];
|
|
943
|
+
const variableStatements = sourceFile.getVariableStatements();
|
|
944
|
+
for (const statement of variableStatements) {
|
|
945
|
+
const declarations = statement.getDeclarations();
|
|
946
|
+
for (const declaration of declarations) {
|
|
947
|
+
const initializer = declaration.getInitializer();
|
|
948
|
+
if (initializer && Node.isCallExpression(initializer)) {
|
|
949
|
+
const expression = initializer.getExpression();
|
|
950
|
+
if (expression.getText() === "pgTable") {
|
|
951
|
+
const tableInfo = this.parsePgTable(declaration.getName(), initializer);
|
|
952
|
+
if (tableInfo) {
|
|
953
|
+
tables.push(tableInfo);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
return tables;
|
|
960
|
+
}
|
|
961
|
+
parsePgTable(variableName, callExpr) {
|
|
962
|
+
const args = callExpr.getArguments();
|
|
963
|
+
if (args.length < 2) {
|
|
964
|
+
return null;
|
|
965
|
+
}
|
|
966
|
+
const tableName = args[0].getText().replace(/['"]/g, "");
|
|
967
|
+
const fieldsArg = args[1];
|
|
968
|
+
if (!Node.isObjectLiteralExpression(fieldsArg)) {
|
|
969
|
+
return null;
|
|
970
|
+
}
|
|
971
|
+
const fields = [];
|
|
972
|
+
const properties = fieldsArg.getProperties();
|
|
973
|
+
for (const prop of properties) {
|
|
974
|
+
if (Node.isPropertyAssignment(prop)) {
|
|
975
|
+
const fieldName = prop.getName();
|
|
976
|
+
const initializer = prop.getInitializer();
|
|
977
|
+
const leadingComments = prop.getLeadingCommentRanges();
|
|
978
|
+
let comment;
|
|
979
|
+
if (leadingComments.length > 0) {
|
|
980
|
+
comment = leadingComments.map((c) => c.getText()).join("\n").replace(/\/\//g, "").trim();
|
|
981
|
+
}
|
|
982
|
+
if (initializer && Node.isCallExpression(initializer)) {
|
|
983
|
+
const fieldInfo = this.parseField(fieldName, initializer, comment);
|
|
984
|
+
fields.push(fieldInfo);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
return {
|
|
989
|
+
tableName,
|
|
990
|
+
variableName,
|
|
991
|
+
fields
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
parseField(fieldName, callExpr, comment) {
|
|
995
|
+
const fieldInfo = {
|
|
996
|
+
name: fieldName,
|
|
997
|
+
columnName: fieldName,
|
|
998
|
+
type: "",
|
|
999
|
+
nullable: true,
|
|
1000
|
+
hasDefault: false,
|
|
1001
|
+
notNull: false,
|
|
1002
|
+
isPrimaryKey: false,
|
|
1003
|
+
isUnique: false,
|
|
1004
|
+
isArray: false,
|
|
1005
|
+
comment
|
|
1006
|
+
};
|
|
1007
|
+
this.parseBaseType(callExpr, fieldInfo);
|
|
1008
|
+
this.parseCallChain(callExpr, fieldInfo);
|
|
1009
|
+
return fieldInfo;
|
|
1010
|
+
}
|
|
1011
|
+
parseBaseType(callExpr, fieldInfo) {
|
|
1012
|
+
let current = callExpr;
|
|
1013
|
+
let baseCall = null;
|
|
1014
|
+
while (Node.isCallExpression(current)) {
|
|
1015
|
+
baseCall = current;
|
|
1016
|
+
const expression2 = current.getExpression();
|
|
1017
|
+
if (Node.isPropertyAccessExpression(expression2)) {
|
|
1018
|
+
current = expression2.getExpression();
|
|
1019
|
+
} else {
|
|
1020
|
+
break;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
if (!baseCall) {
|
|
1024
|
+
return;
|
|
1025
|
+
}
|
|
1026
|
+
const expression = baseCall.getExpression();
|
|
1027
|
+
let typeName = "";
|
|
1028
|
+
if (Node.isPropertyAccessExpression(expression)) {
|
|
1029
|
+
typeName = expression.getName();
|
|
1030
|
+
} else {
|
|
1031
|
+
typeName = expression.getText();
|
|
1032
|
+
}
|
|
1033
|
+
fieldInfo.type = typeName;
|
|
1034
|
+
const args = baseCall.getArguments();
|
|
1035
|
+
if (args.length > 0) {
|
|
1036
|
+
const firstArg = args[0];
|
|
1037
|
+
if (Node.isStringLiteral(firstArg)) {
|
|
1038
|
+
fieldInfo.columnName = firstArg.getLiteralText();
|
|
1039
|
+
} else if (Node.isObjectLiteralExpression(firstArg)) {
|
|
1040
|
+
this.parseTypeConfig(firstArg, fieldInfo);
|
|
1041
|
+
} else if (Node.isArrayLiteralExpression(firstArg)) {
|
|
1042
|
+
fieldInfo.enumValues = firstArg.getElements().map((el) => el.getText().replace(/['"]/g, ""));
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
if (args.length > 1 && Node.isObjectLiteralExpression(args[1])) {
|
|
1046
|
+
this.parseTypeConfig(args[1], fieldInfo);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
parseTypeConfig(objLiteral, fieldInfo) {
|
|
1050
|
+
if (!Node.isObjectLiteralExpression(objLiteral)) {
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
const properties = objLiteral.getProperties();
|
|
1054
|
+
for (const prop of properties) {
|
|
1055
|
+
if (Node.isPropertyAssignment(prop)) {
|
|
1056
|
+
const propName = prop.getName();
|
|
1057
|
+
const value = prop.getInitializer()?.getText();
|
|
1058
|
+
switch (propName) {
|
|
1059
|
+
case "length":
|
|
1060
|
+
fieldInfo.length = value ? parseInt(value) : void 0;
|
|
1061
|
+
break;
|
|
1062
|
+
case "precision":
|
|
1063
|
+
fieldInfo.precision = value ? parseInt(value) : void 0;
|
|
1064
|
+
break;
|
|
1065
|
+
case "scale":
|
|
1066
|
+
fieldInfo.scale = value ? parseInt(value) : void 0;
|
|
1067
|
+
break;
|
|
1068
|
+
case "default":
|
|
1069
|
+
fieldInfo.hasDefault = true;
|
|
1070
|
+
fieldInfo.defaultValue = value;
|
|
1071
|
+
break;
|
|
1072
|
+
// 时间精度(用于 timestamp, time 等)
|
|
1073
|
+
case "withTimezone":
|
|
1074
|
+
fieldInfo.withTimezone = value === "true";
|
|
1075
|
+
break;
|
|
1076
|
+
case "mode":
|
|
1077
|
+
fieldInfo.mode = value?.replace(/['"]/g, "");
|
|
1078
|
+
break;
|
|
1079
|
+
default:
|
|
1080
|
+
throw new Error(`Unsupported property: ${propName}`);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
parseCallChain(callExpr, fieldInfo) {
|
|
1086
|
+
let current = callExpr;
|
|
1087
|
+
while (Node.isCallExpression(current)) {
|
|
1088
|
+
const expression = current.getExpression();
|
|
1089
|
+
if (Node.isPropertyAccessExpression(expression)) {
|
|
1090
|
+
const methodName = expression.getName();
|
|
1091
|
+
const args = current.getArguments();
|
|
1092
|
+
switch (methodName) {
|
|
1093
|
+
case "notNull":
|
|
1094
|
+
fieldInfo.notNull = true;
|
|
1095
|
+
fieldInfo.nullable = false;
|
|
1096
|
+
break;
|
|
1097
|
+
case "default":
|
|
1098
|
+
fieldInfo.hasDefault = true;
|
|
1099
|
+
if (args.length > 0) {
|
|
1100
|
+
fieldInfo.defaultValue = args[0].getText();
|
|
1101
|
+
}
|
|
1102
|
+
break;
|
|
1103
|
+
case "defaultRandom":
|
|
1104
|
+
fieldInfo.hasDefault = true;
|
|
1105
|
+
fieldInfo.defaultValue = "random";
|
|
1106
|
+
break;
|
|
1107
|
+
case "primaryKey":
|
|
1108
|
+
fieldInfo.isPrimaryKey = true;
|
|
1109
|
+
fieldInfo.notNull = true;
|
|
1110
|
+
fieldInfo.nullable = false;
|
|
1111
|
+
break;
|
|
1112
|
+
case "unique":
|
|
1113
|
+
fieldInfo.isUnique = true;
|
|
1114
|
+
break;
|
|
1115
|
+
case "array":
|
|
1116
|
+
fieldInfo.isArray = true;
|
|
1117
|
+
break;
|
|
1118
|
+
case "references":
|
|
1119
|
+
if (args.length > 0) {
|
|
1120
|
+
const refArg = args[0].getText();
|
|
1121
|
+
const match = refArg.match(/=>\s*(\w+)\.(\w+)/);
|
|
1122
|
+
if (match) {
|
|
1123
|
+
fieldInfo.references = {
|
|
1124
|
+
table: match[1],
|
|
1125
|
+
column: match[2]
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
break;
|
|
1130
|
+
default:
|
|
1131
|
+
throw new Error(`Unsupported method: ${methodName}`);
|
|
1132
|
+
}
|
|
1133
|
+
current = expression.getExpression();
|
|
1134
|
+
} else {
|
|
1135
|
+
break;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
};
|
|
1140
|
+
|
|
1141
|
+
// src/helpers/gen-nest-resource/index.ts
|
|
1142
|
+
import { join } from "path";
|
|
1143
|
+
import { mkdir, rm, writeFile } from "fs/promises";
|
|
1144
|
+
import { existsSync } from "fs";
|
|
1145
|
+
async function parseAndGenerateNestResourceTemplate(options) {
|
|
1146
|
+
const parser = new DrizzleSchemaParser({
|
|
1147
|
+
tsConfigFilePath: options.tsConfigFilePath
|
|
1148
|
+
});
|
|
1149
|
+
const tables = parser.parseSchemaFile(options.schemaFilePath);
|
|
1150
|
+
for (const table of tables) {
|
|
1151
|
+
console.info(`\u751F\u6210 Nest.js ${table.variableName} \u6A21\u5757`);
|
|
1152
|
+
const routePath = toKebabCase(table.variableName);
|
|
1153
|
+
const moduleDir = join(options.moduleOutputDir, routePath);
|
|
1154
|
+
if (existsSync(moduleDir)) {
|
|
1155
|
+
console.info(`Nest.js \u6A21\u5757 ${routePath} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u751F\u6210\u4EE3\u7801`);
|
|
1156
|
+
continue;
|
|
1157
|
+
}
|
|
1158
|
+
const dto = generateDTO(table);
|
|
1159
|
+
const controller = generateController(table);
|
|
1160
|
+
const service = generateService(table);
|
|
1161
|
+
const moduleFilePath = join(moduleDir, `${routePath}.module.ts`);
|
|
1162
|
+
const module = generateModule(table);
|
|
1163
|
+
try {
|
|
1164
|
+
await mkdir(moduleDir, {
|
|
1165
|
+
recursive: true
|
|
1166
|
+
});
|
|
1167
|
+
await mkdir(join(moduleDir, "dtos"), {
|
|
1168
|
+
recursive: true
|
|
1169
|
+
});
|
|
1170
|
+
await writeFile(join(moduleDir, "dtos", `${routePath}.dto.ts`), dto);
|
|
1171
|
+
await writeFile(join(moduleDir, `${routePath}.controller.ts`), controller);
|
|
1172
|
+
await writeFile(join(moduleDir, `${routePath}.service.ts`), service);
|
|
1173
|
+
await writeFile(moduleFilePath, module);
|
|
1174
|
+
} catch (err) {
|
|
1175
|
+
console.error(`\u751F\u6210 Nest.js ${routePath} \u6A21\u5757\u5931\u8D25: ${err.message}`);
|
|
1176
|
+
await rm(moduleDir, {
|
|
1177
|
+
recursive: true
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
__name(parseAndGenerateNestResourceTemplate, "parseAndGenerateNestResourceTemplate");
|
|
1183
|
+
|
|
517
1184
|
// src/helpers/proxy-error/index.ts
|
|
518
1185
|
import fs3 from "fs";
|
|
519
1186
|
import path3 from "path";
|
|
@@ -885,7 +1552,7 @@ async function enhanceOpenApiWithSourceInfo(options = {}) {
|
|
|
885
1552
|
const startTime = Date.now();
|
|
886
1553
|
const openapiPath = options.openapiPath || path5.resolve(__dirname, "../client/src/api/gen/openapi.json");
|
|
887
1554
|
const serverDir = options.serverDir || path5.resolve(__dirname, "../server");
|
|
888
|
-
const
|
|
1555
|
+
const writeFile2 = options.writeFile !== false;
|
|
889
1556
|
let openapi;
|
|
890
1557
|
if (options.openapiData) {
|
|
891
1558
|
openapi = JSON.parse(JSON.stringify(options.openapiData));
|
|
@@ -896,7 +1563,7 @@ async function enhanceOpenApiWithSourceInfo(options = {}) {
|
|
|
896
1563
|
const controllerFiles = await findControllerFiles(serverDir);
|
|
897
1564
|
const sourceMap = await buildSourceMap(controllerFiles, processControllerFile);
|
|
898
1565
|
const enhanced = enhanceOpenApiPaths(openapi, sourceMap);
|
|
899
|
-
if (
|
|
1566
|
+
if (writeFile2) {
|
|
900
1567
|
await fs5.writeFile(openapiPath, JSON.stringify(openapi, null, 2) + "\n", "utf-8");
|
|
901
1568
|
}
|
|
902
1569
|
const duration = Date.now() - startTime;
|
|
@@ -1079,7 +1746,7 @@ import express2 from "express";
|
|
|
1079
1746
|
|
|
1080
1747
|
// src/middlewares/dev-logs/utils.ts
|
|
1081
1748
|
import { promises as fs7 } from "fs";
|
|
1082
|
-
import { isAbsolute, join, relative } from "path";
|
|
1749
|
+
import { isAbsolute, join as join2, relative } from "path";
|
|
1083
1750
|
|
|
1084
1751
|
// src/middlewares/dev-logs/helper/path-matcher.ts
|
|
1085
1752
|
function pathPatternToRegex(pattern) {
|
|
@@ -1119,9 +1786,9 @@ __name(normalizePathForMatching, "normalizePathForMatching");
|
|
|
1119
1786
|
// src/middlewares/dev-logs/utils.ts
|
|
1120
1787
|
function resolveLogDir(provided) {
|
|
1121
1788
|
if (!provided) {
|
|
1122
|
-
return
|
|
1789
|
+
return join2(process.cwd(), "logs");
|
|
1123
1790
|
}
|
|
1124
|
-
return isAbsolute(provided) ? provided :
|
|
1791
|
+
return isAbsolute(provided) ? provided : join2(process.cwd(), provided);
|
|
1125
1792
|
}
|
|
1126
1793
|
__name(resolveLogDir, "resolveLogDir");
|
|
1127
1794
|
function getRelativePath(filePath) {
|
|
@@ -1180,7 +1847,7 @@ function resolveLogFilePath(baseDir, fileName) {
|
|
|
1180
1847
|
if (segments.some((segment) => segment === "..")) {
|
|
1181
1848
|
throw new Error("Invalid log file path");
|
|
1182
1849
|
}
|
|
1183
|
-
const resolved =
|
|
1850
|
+
const resolved = join2(baseDir, segments.join("/"));
|
|
1184
1851
|
const rel = relative(baseDir, resolved);
|
|
1185
1852
|
if (rel.startsWith("..")) {
|
|
1186
1853
|
throw new Error("Access to the specified log file is denied");
|
|
@@ -1210,7 +1877,7 @@ function serializeError(error) {
|
|
|
1210
1877
|
__name(serializeError, "serializeError");
|
|
1211
1878
|
|
|
1212
1879
|
// src/middlewares/dev-logs/controller.ts
|
|
1213
|
-
import { join as
|
|
1880
|
+
import { join as join3 } from "path";
|
|
1214
1881
|
|
|
1215
1882
|
// src/middlewares/dev-logs/services.ts
|
|
1216
1883
|
import { createReadStream, promises as fs8 } from "fs";
|
|
@@ -1439,7 +2106,7 @@ function handleError(res, error, message = "Failed to read log file") {
|
|
|
1439
2106
|
}
|
|
1440
2107
|
__name(handleError, "handleError");
|
|
1441
2108
|
function createGetTraceEntriesHandler(logDir) {
|
|
1442
|
-
const appLogPath =
|
|
2109
|
+
const appLogPath = join3(logDir, "server.log");
|
|
1443
2110
|
return async (req, res) => {
|
|
1444
2111
|
const traceId = (req.params.traceId || "").trim();
|
|
1445
2112
|
if (!traceId) {
|
|
@@ -1466,7 +2133,7 @@ function createGetTraceEntriesHandler(logDir) {
|
|
|
1466
2133
|
}
|
|
1467
2134
|
__name(createGetTraceEntriesHandler, "createGetTraceEntriesHandler");
|
|
1468
2135
|
function createGetRecentTracesHandler(logDir) {
|
|
1469
|
-
const traceLogPath =
|
|
2136
|
+
const traceLogPath = join3(logDir, "trace.log");
|
|
1470
2137
|
return async (req, res) => {
|
|
1471
2138
|
const page = parsePositiveInt(req.query.page, 1);
|
|
1472
2139
|
const pageSize = parseLimit(req.query.pageSize, 10, 100);
|
|
@@ -1634,17 +2301,17 @@ __name(createDevLogsMiddleware, "createDevLogsMiddleware");
|
|
|
1634
2301
|
import express3 from "express";
|
|
1635
2302
|
|
|
1636
2303
|
// src/middlewares/collect-logs/controller.ts
|
|
1637
|
-
import { join as
|
|
2304
|
+
import { join as join5 } from "path";
|
|
1638
2305
|
import fs10 from "fs";
|
|
1639
2306
|
|
|
1640
2307
|
// src/middlewares/collect-logs/utils.ts
|
|
1641
|
-
import { isAbsolute as isAbsolute2, join as
|
|
2308
|
+
import { isAbsolute as isAbsolute2, join as join4 } from "path";
|
|
1642
2309
|
import fs9 from "fs";
|
|
1643
2310
|
function resolveLogDir2(provided) {
|
|
1644
2311
|
if (!provided) {
|
|
1645
|
-
return
|
|
2312
|
+
return join4(process.cwd(), "logs");
|
|
1646
2313
|
}
|
|
1647
|
-
return isAbsolute2(provided) ? provided :
|
|
2314
|
+
return isAbsolute2(provided) ? provided : join4(process.cwd(), provided);
|
|
1648
2315
|
}
|
|
1649
2316
|
__name(resolveLogDir2, "resolveLogDir");
|
|
1650
2317
|
function ensureDir(dir) {
|
|
@@ -1667,7 +2334,7 @@ __name(serializeError2, "serializeError");
|
|
|
1667
2334
|
|
|
1668
2335
|
// src/middlewares/collect-logs/controller.ts
|
|
1669
2336
|
function collectLogsHandler(logDir, fileName) {
|
|
1670
|
-
const filePath =
|
|
2337
|
+
const filePath = join5(logDir, fileName);
|
|
1671
2338
|
ensureDir(logDir);
|
|
1672
2339
|
return async (req, res) => {
|
|
1673
2340
|
try {
|
|
@@ -1692,7 +2359,7 @@ function collectLogsHandler(logDir, fileName) {
|
|
|
1692
2359
|
}
|
|
1693
2360
|
__name(collectLogsHandler, "collectLogsHandler");
|
|
1694
2361
|
function collectLogsBatchHandler(logDir, fileName) {
|
|
1695
|
-
const filePath =
|
|
2362
|
+
const filePath = join5(logDir, fileName);
|
|
1696
2363
|
ensureDir(logDir);
|
|
1697
2364
|
return async (req, res) => {
|
|
1698
2365
|
try {
|
|
@@ -1847,6 +2514,7 @@ export {
|
|
|
1847
2514
|
createOpenapiMiddleware,
|
|
1848
2515
|
handleDevProxyError,
|
|
1849
2516
|
normalizeBasePath,
|
|
2517
|
+
parseAndGenerateNestResourceTemplate,
|
|
1850
2518
|
postprocessDrizzleSchema,
|
|
1851
2519
|
registerMiddlewares
|
|
1852
2520
|
};
|