@arcteninc/core 0.0.46 → 0.0.48
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/package.json +2 -1
- package/scripts/cli-extract-types-auto.ts +193 -631
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcteninc/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.48",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"ajv": "^8.17.1",
|
|
47
47
|
"react": "^19.2.0",
|
|
48
48
|
"react-dom": "^19.2.0",
|
|
49
|
+
"ts-json-schema-generator": "^2.4.0",
|
|
49
50
|
"typescript": "^5.9.3",
|
|
50
51
|
"vite": "^7.1.12",
|
|
51
52
|
"vite-plugin-dts": "^4.5.4"
|
|
@@ -9,6 +9,25 @@ import * as fs from 'fs';
|
|
|
9
9
|
import * as path from 'path';
|
|
10
10
|
import { glob } from 'glob';
|
|
11
11
|
|
|
12
|
+
// Lazy import for ts-json-schema-generator (optional - falls back to custom serializer if not available)
|
|
13
|
+
let tsJsonSchemaGeneratorModule: any = null;
|
|
14
|
+
let generatorAvailable = false;
|
|
15
|
+
|
|
16
|
+
async function ensureGeneratorLoaded(): Promise<boolean> {
|
|
17
|
+
if (generatorAvailable) return true;
|
|
18
|
+
if (tsJsonSchemaGeneratorModule !== null) return false; // Already tried and failed
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
// @ts-ignore - ts-json-schema-generator is optional
|
|
22
|
+
tsJsonSchemaGeneratorModule = await import('ts-json-schema-generator');
|
|
23
|
+
generatorAvailable = true;
|
|
24
|
+
return true;
|
|
25
|
+
} catch (error) {
|
|
26
|
+
tsJsonSchemaGeneratorModule = false; // Mark as failed
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
12
31
|
// JSON Schema compatible format
|
|
13
32
|
interface JsonSchemaProperty {
|
|
14
33
|
type?: string | string[]; // Can be string, array of strings, or omitted for anyOf/oneOf/$ref
|
|
@@ -553,692 +572,233 @@ function resolveImportPath(
|
|
|
553
572
|
return null;
|
|
554
573
|
}
|
|
555
574
|
|
|
556
|
-
// Constants for JSON Schema validation
|
|
557
|
-
// These are actual constants from the JSON Schema specification
|
|
558
|
-
const VALID_JSON_SCHEMA_TYPES = new Set(['string', 'number', 'integer', 'boolean', 'object', 'array', 'null']);
|
|
559
|
-
|
|
560
|
-
// Safety limit for serialization depth (only used as a last resort)
|
|
561
|
-
// Note: Cycle detection via 'visited' Set is the primary protection against infinite recursion
|
|
562
|
-
// This is only a fallback safety net for edge cases
|
|
563
|
-
const MAX_SERIALIZATION_DEPTH = 50; // Increased from 10 - legitimate types can be deeply nested
|
|
564
|
-
|
|
565
|
-
// Note: MAX_TOOL_NAME_LENGTH was removed - we now use proper AST node type checks
|
|
566
|
-
// instead of string length heuristics for better accuracy
|
|
567
|
-
|
|
568
|
-
/**
|
|
569
|
-
* Type-safe accessors for TypeScript internal APIs
|
|
570
|
-
* These use type guards to safely access properties that aren't in the public API
|
|
571
|
-
*/
|
|
572
|
-
|
|
573
|
-
interface BooleanLiteralType extends ts.Type {
|
|
574
|
-
intrinsicName: 'true' | 'false';
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
interface NumberLiteralType extends ts.Type {
|
|
578
|
-
value: number;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
interface StringLiteralType extends ts.Type {
|
|
582
|
-
value: string;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
/**
|
|
586
|
-
* Get boolean literal value from a TypeScript type
|
|
587
|
-
*/
|
|
588
|
-
function getBooleanLiteralValue(type: ts.Type): boolean | null {
|
|
589
|
-
if (type.flags & ts.TypeFlags.BooleanLiteral) {
|
|
590
|
-
const boolType = type as BooleanLiteralType;
|
|
591
|
-
return boolType.intrinsicName === 'true';
|
|
592
|
-
}
|
|
593
|
-
return null;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
/**
|
|
597
|
-
* Get number literal value from a TypeScript type
|
|
598
|
-
*/
|
|
599
|
-
function getNumberLiteralValue(type: ts.Type): number | null {
|
|
600
|
-
if (type.flags & ts.TypeFlags.NumberLiteral) {
|
|
601
|
-
const numType = type as NumberLiteralType;
|
|
602
|
-
return numType.value;
|
|
603
|
-
}
|
|
604
|
-
return null;
|
|
605
|
-
}
|
|
606
|
-
|
|
607
575
|
/**
|
|
608
|
-
*
|
|
576
|
+
* Try to resolve an anonymous type to a named type by checking structural compatibility
|
|
577
|
+
* This helps when function parameters use anonymous types that match existing interfaces
|
|
609
578
|
*/
|
|
610
|
-
function
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
579
|
+
function tryResolveToNamedType(
|
|
580
|
+
type: ts.Type,
|
|
581
|
+
checker: ts.TypeChecker,
|
|
582
|
+
sourceFile: ts.SourceFile
|
|
583
|
+
): { typeName: string; sourceFile: string } | null {
|
|
584
|
+
// Only try for object types
|
|
585
|
+
if (!(type.flags & ts.TypeFlags.Object)) return null;
|
|
586
|
+
|
|
587
|
+
const props = checker.getPropertiesOfType(type);
|
|
588
|
+
if (props.length === 0) return null;
|
|
589
|
+
|
|
590
|
+
// Get all exported interfaces/types from the same file
|
|
591
|
+
const moduleSymbol = checker.getSymbolAtLocation(sourceFile);
|
|
592
|
+
if (!moduleSymbol) return null;
|
|
593
|
+
|
|
594
|
+
const fileSymbols = checker.getExportsOfModule(moduleSymbol);
|
|
595
|
+
if (!fileSymbols || fileSymbols.length === 0) return null;
|
|
596
|
+
|
|
597
|
+
// Check each exported type/interface to see if it matches structurally
|
|
598
|
+
for (const symbol of fileSymbols) {
|
|
599
|
+
if (!symbol) continue;
|
|
600
|
+
|
|
601
|
+
const name = symbol.getName();
|
|
602
|
+
if (!name || name.length === 0) continue;
|
|
603
|
+
|
|
604
|
+
const declarations = symbol.getDeclarations();
|
|
605
|
+
if (!declarations || declarations.length === 0) continue;
|
|
606
|
+
|
|
607
|
+
for (const decl of declarations) {
|
|
608
|
+
if (ts.isInterfaceDeclaration(decl) || ts.isTypeAliasDeclaration(decl)) {
|
|
609
|
+
// Get the type from the declaration
|
|
610
|
+
const declaredType = checker.getTypeAtLocation(decl);
|
|
611
|
+
|
|
612
|
+
// Check if properties match (simple structural check)
|
|
613
|
+
const declaredProps = checker.getPropertiesOfType(declaredType);
|
|
614
|
+
if (declaredProps.length === props.length) {
|
|
615
|
+
// Check if all property names match
|
|
616
|
+
const propNames = new Set(props.map(p => p.getName()));
|
|
617
|
+
const declaredPropNames = new Set(declaredProps.map(p => p.getName()));
|
|
618
|
+
|
|
619
|
+
if (propNames.size === declaredPropNames.size &&
|
|
620
|
+
[...propNames].every(n => declaredPropNames.has(n))) {
|
|
621
|
+
// Properties match - this might be the same type
|
|
622
|
+
return {
|
|
623
|
+
typeName: name,
|
|
624
|
+
sourceFile: sourceFile.fileName,
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
614
630
|
}
|
|
631
|
+
|
|
615
632
|
return null;
|
|
616
633
|
}
|
|
617
634
|
|
|
618
635
|
/**
|
|
619
|
-
*
|
|
620
|
-
* Uses TypeScript's internal API - this is necessary for cycle detection
|
|
621
|
-
* but we wrap it in a function to centralize the unsafe access
|
|
622
|
-
*/
|
|
623
|
-
function getTypeId(type: ts.Type): number | undefined {
|
|
624
|
-
// TypeScript's Type interface doesn't expose 'id' in public API,
|
|
625
|
-
// but it's available internally and needed for cycle detection.
|
|
626
|
-
// This is a known limitation when working with TypeScript's compiler API.
|
|
627
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
628
|
-
return (type as any).id;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
/**
|
|
632
|
-
* Get a stable name for a type to use in $defs
|
|
633
|
-
* Returns the type name if it's a named type (interface, type alias, class)
|
|
634
|
-
* Otherwise returns a generated name based on type ID
|
|
636
|
+
* Try to get type name and source file for use with ts-json-schema-generator
|
|
635
637
|
*/
|
|
636
|
-
function
|
|
638
|
+
function getTypeInfoForGenerator(
|
|
637
639
|
type: ts.Type,
|
|
638
|
-
checker: ts.TypeChecker
|
|
639
|
-
|
|
640
|
+
checker: ts.TypeChecker,
|
|
641
|
+
paramSourceFile?: ts.SourceFile
|
|
642
|
+
): { typeName: string; sourceFile: string } | null {
|
|
640
643
|
const symbol = type.getSymbol();
|
|
644
|
+
|
|
645
|
+
// If it's a named type, use it directly
|
|
641
646
|
if (symbol) {
|
|
642
647
|
const name = symbol.getName();
|
|
643
648
|
if (name && name.length > 0) {
|
|
644
|
-
// Check if it's a named type (not a primitive)
|
|
645
649
|
const declarations = symbol.getDeclarations();
|
|
646
650
|
if (declarations && declarations.length > 0) {
|
|
647
|
-
//
|
|
651
|
+
// Find the first declaration that's a type alias, interface, or class
|
|
648
652
|
for (const decl of declarations) {
|
|
649
653
|
if (
|
|
650
654
|
ts.isTypeAliasDeclaration(decl) ||
|
|
651
655
|
ts.isInterfaceDeclaration(decl) ||
|
|
652
656
|
ts.isClassDeclaration(decl)
|
|
653
657
|
) {
|
|
654
|
-
|
|
658
|
+
const sourceFile = decl.getSourceFile();
|
|
659
|
+
if (sourceFile) {
|
|
660
|
+
return {
|
|
661
|
+
typeName: name,
|
|
662
|
+
sourceFile: sourceFile.fileName,
|
|
663
|
+
};
|
|
664
|
+
}
|
|
655
665
|
}
|
|
656
666
|
}
|
|
657
667
|
}
|
|
658
668
|
}
|
|
659
669
|
}
|
|
660
670
|
|
|
661
|
-
//
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
!typeString.includes('<') && !typeString.includes('(') &&
|
|
666
|
-
typeString.length < 50 && /^[A-Za-z_][A-Za-z0-9_]*$/.test(typeString)) {
|
|
667
|
-
return typeString;
|
|
671
|
+
// If it's an anonymous type, try to resolve it to a named type
|
|
672
|
+
if (paramSourceFile) {
|
|
673
|
+
const resolved = tryResolveToNamedType(type, checker, paramSourceFile);
|
|
674
|
+
if (resolved) return resolved;
|
|
668
675
|
}
|
|
669
676
|
|
|
670
677
|
return null;
|
|
671
678
|
}
|
|
672
679
|
|
|
673
680
|
/**
|
|
674
|
-
* Serialize a
|
|
675
|
-
* Returns null if
|
|
681
|
+
* Serialize a TypeScript type to JSON Schema format using ts-json-schema-generator
|
|
682
|
+
* Returns: { isOptional, schema } or null if generator not available
|
|
683
|
+
* Note: This is async and should only be called at top level
|
|
676
684
|
*/
|
|
677
|
-
function
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
if
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
const strValue = getStringLiteralValue(type);
|
|
689
|
-
if (strValue !== null) {
|
|
690
|
-
return { type: 'string', const: strValue };
|
|
685
|
+
async function serializeTypeWithGenerator(
|
|
686
|
+
type: ts.Type,
|
|
687
|
+
checker: ts.TypeChecker,
|
|
688
|
+
program: ts.Program,
|
|
689
|
+
configPath: string | undefined,
|
|
690
|
+
defs?: Record<string, JsonSchemaProperty>
|
|
691
|
+
): Promise<{ isOptional: boolean; schema: JsonSchemaProperty } | null> {
|
|
692
|
+
// Try to load generator if not already loaded
|
|
693
|
+
const generatorLoaded = await ensureGeneratorLoaded();
|
|
694
|
+
if (!generatorLoaded || !tsJsonSchemaGeneratorModule) {
|
|
695
|
+
return null;
|
|
691
696
|
}
|
|
692
|
-
|
|
693
|
-
return null;
|
|
694
|
-
}
|
|
695
697
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
* Uses TypeScript API to determine if it's a named type reference
|
|
699
|
-
*/
|
|
700
|
-
function isTypeReference(type: ts.Type, checker: ts.TypeChecker): boolean {
|
|
698
|
+
// Try to get source file from the type's declarations or use a fallback
|
|
699
|
+
let sourceFileForLookup: ts.SourceFile | undefined;
|
|
701
700
|
const symbol = type.getSymbol();
|
|
702
|
-
if (
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
// Check if it's a primitive JSON Schema type (should not be treated as type reference)
|
|
708
|
-
if (VALID_JSON_SCHEMA_TYPES.has(name.toLowerCase())) {
|
|
709
|
-
return false;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
// Check if it's a named type (has declarations and is not a primitive)
|
|
713
|
-
const declarations = symbol.getDeclarations();
|
|
714
|
-
if (!declarations || declarations.length === 0) return false;
|
|
715
|
-
|
|
716
|
-
// Check if any declaration is a type alias, interface, or class
|
|
717
|
-
// This is more reliable than regex pattern matching
|
|
718
|
-
for (const decl of declarations) {
|
|
719
|
-
if (
|
|
720
|
-
ts.isTypeAliasDeclaration(decl) ||
|
|
721
|
-
ts.isInterfaceDeclaration(decl) ||
|
|
722
|
-
ts.isClassDeclaration(decl) ||
|
|
723
|
-
ts.isEnumDeclaration(decl)
|
|
724
|
-
) {
|
|
725
|
-
// Additional check: PascalCase convention (first letter uppercase)
|
|
726
|
-
// This helps distinguish type references from variables/functions
|
|
727
|
-
const firstChar = name.charAt(0);
|
|
728
|
-
return firstChar >= 'A' && firstChar <= 'Z';
|
|
701
|
+
if (symbol) {
|
|
702
|
+
const declarations = symbol.getDeclarations();
|
|
703
|
+
if (declarations && declarations.length > 0) {
|
|
704
|
+
sourceFileForLookup = declarations[0].getSourceFile();
|
|
729
705
|
}
|
|
730
706
|
}
|
|
731
707
|
|
|
732
|
-
|
|
733
|
-
|
|
708
|
+
const typeInfo = getTypeInfoForGenerator(type, checker, sourceFileForLookup);
|
|
709
|
+
if (!typeInfo) return null;
|
|
710
|
+
|
|
711
|
+
try {
|
|
712
|
+
// Create generator config
|
|
713
|
+
const generatorConfig: any = {
|
|
714
|
+
path: typeInfo.sourceFile,
|
|
715
|
+
tsconfig: configPath || 'tsconfig.json',
|
|
716
|
+
type: typeInfo.typeName,
|
|
717
|
+
expose: 'all',
|
|
718
|
+
jsDoc: 'extended',
|
|
719
|
+
topRef: true,
|
|
720
|
+
skipTypeCheck: true,
|
|
721
|
+
additionalProperties: false,
|
|
722
|
+
};
|
|
734
723
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
* Uses type checking instead of string length heuristics
|
|
738
|
-
*/
|
|
739
|
-
function isFallbackAnySchema(
|
|
740
|
-
schema: JsonSchemaProperty,
|
|
741
|
-
type: ts.Type,
|
|
742
|
-
checker: ts.TypeChecker
|
|
743
|
-
): boolean {
|
|
744
|
-
// Empty schema {} means "any value" in JSON Schema draft 2020-12
|
|
745
|
-
// If schema is empty (no keys), it's a fallback "any" schema
|
|
746
|
-
if (Object.keys(schema).length === 0) {
|
|
747
|
-
return true;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
// If it has properties, anyOf, or items, it's a real schema
|
|
751
|
-
if (schema.properties || schema.anyOf || schema.items) {
|
|
752
|
-
return false;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
// If it has a type property (and it's not 'any'), it's a real schema
|
|
756
|
-
if (schema.type && schema.type !== 'any') {
|
|
757
|
-
return false;
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
// Check if it's actually a type reference we couldn't resolve
|
|
761
|
-
// (not a primitive, not an object we processed, etc.)
|
|
762
|
-
return isTypeReference(type, checker);
|
|
763
|
-
}
|
|
724
|
+
const generator = tsJsonSchemaGeneratorModule.createGenerator(generatorConfig);
|
|
725
|
+
const schema = generator.createSchema(typeInfo.typeName);
|
|
764
726
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
type: ts.Type,
|
|
770
|
-
checker: ts.TypeChecker
|
|
771
|
-
): ts.Type | null {
|
|
772
|
-
const symbol = type.getSymbol();
|
|
773
|
-
if (!symbol) return null;
|
|
727
|
+
// Extract $defs if present and merge into our defs
|
|
728
|
+
if (schema.$defs && defs) {
|
|
729
|
+
Object.assign(defs, schema.$defs);
|
|
730
|
+
}
|
|
774
731
|
|
|
775
|
-
|
|
776
|
-
|
|
732
|
+
// Remove $defs from the schema itself (we'll add it at the top level)
|
|
733
|
+
const { $defs, ...schemaWithoutDefs } = schema as any;
|
|
777
734
|
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
// Resolve the type alias
|
|
782
|
-
return checker.getTypeFromTypeNode(decl.type);
|
|
783
|
-
}
|
|
784
|
-
if (ts.isInterfaceDeclaration(decl)) {
|
|
785
|
-
// Get the interface type
|
|
786
|
-
return checker.getTypeAtLocation(decl);
|
|
787
|
-
}
|
|
788
|
-
}
|
|
735
|
+
// Handle optional (check if type includes undefined)
|
|
736
|
+
const isOptional = type.flags & ts.TypeFlags.Undefined ||
|
|
737
|
+
(type.isUnion() && type.types.some(t => t.flags & ts.TypeFlags.Undefined));
|
|
789
738
|
|
|
790
|
-
|
|
739
|
+
return {
|
|
740
|
+
isOptional: !!isOptional,
|
|
741
|
+
schema: schemaWithoutDefs as JsonSchemaProperty,
|
|
742
|
+
};
|
|
743
|
+
} catch (error) {
|
|
744
|
+
// If generator fails, return null (will result in empty schema)
|
|
745
|
+
return null;
|
|
746
|
+
}
|
|
791
747
|
}
|
|
792
748
|
|
|
749
|
+
// Cache for generator results to avoid repeated calls
|
|
750
|
+
const generatorCache = new Map<string, { isOptional: boolean; schema: JsonSchemaProperty }>();
|
|
751
|
+
|
|
793
752
|
/**
|
|
794
|
-
* Serialize a TypeScript type to JSON Schema format
|
|
795
|
-
*
|
|
753
|
+
* Serialize a TypeScript type to JSON Schema format using ts-json-schema-generator
|
|
754
|
+
* Only works for named types (interfaces, type aliases, classes)
|
|
755
|
+
* Returns empty schema for anonymous types
|
|
796
756
|
*/
|
|
797
|
-
function serializeType(
|
|
757
|
+
async function serializeType(
|
|
798
758
|
type: ts.Type,
|
|
799
759
|
checker: ts.TypeChecker,
|
|
800
|
-
|
|
801
|
-
|
|
760
|
+
program: ts.Program | undefined,
|
|
761
|
+
configPath: string | undefined,
|
|
802
762
|
defs?: Record<string, JsonSchemaProperty>,
|
|
803
|
-
|
|
804
|
-
): { isOptional: boolean; schema: JsonSchemaProperty } {
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
if (depth > MAX_SERIALIZATION_DEPTH) {
|
|
808
|
-
return { isOptional: false, schema: {} }; // Empty schema = any value (JSON Schema draft 2020-12)
|
|
763
|
+
sourceFileForLookup?: ts.SourceFile
|
|
764
|
+
): Promise<{ isOptional: boolean; schema: JsonSchemaProperty }> {
|
|
765
|
+
if (!program || !configPath) {
|
|
766
|
+
return { isOptional: false, schema: {} }; // Empty schema = any value
|
|
809
767
|
}
|
|
810
768
|
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
//
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
if (defs[typeName]) {
|
|
821
|
-
return { isOptional: false, schema: { $ref: `#/$defs/${typeName}` } };
|
|
822
|
-
}
|
|
823
|
-
// If we're currently building this definition, use $ref (circular reference)
|
|
824
|
-
if (defsVisited.has(typeId)) {
|
|
825
|
-
return { isOptional: false, schema: { $ref: `#/$defs/${typeName}` } };
|
|
826
|
-
}
|
|
827
|
-
}
|
|
769
|
+
// Use provided sourceFileForLookup or try to find one from program
|
|
770
|
+
let lookupFile = sourceFileForLookup;
|
|
771
|
+
if (!lookupFile && program) {
|
|
772
|
+
// Try to find a source file that might contain the type
|
|
773
|
+
// This is a best-effort approach for anonymous types
|
|
774
|
+
const sourceFiles = program.getSourceFiles();
|
|
775
|
+
if (sourceFiles.length > 0) {
|
|
776
|
+
// Use the first source file as a fallback (could be improved)
|
|
777
|
+
lookupFile = sourceFiles[0];
|
|
828
778
|
}
|
|
829
|
-
// Fallback: empty schema for anonymous recursive types
|
|
830
|
-
return { isOptional: false, schema: {} }; // Empty schema = any value (JSON Schema draft 2020-12)
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
// Handle primitives
|
|
834
|
-
if (type.flags & ts.TypeFlags.String) {
|
|
835
|
-
return { isOptional: false, schema: { type: 'string' } };
|
|
836
|
-
}
|
|
837
|
-
if (type.flags & ts.TypeFlags.Number) {
|
|
838
|
-
return { isOptional: false, schema: { type: 'number' } };
|
|
839
|
-
}
|
|
840
|
-
if (type.flags & ts.TypeFlags.Boolean) {
|
|
841
|
-
return { isOptional: false, schema: { type: 'boolean' } };
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
// Handle literals using helper function
|
|
845
|
-
const literalSchema = serializeLiteralType(type);
|
|
846
|
-
if (literalSchema) {
|
|
847
|
-
return { isOptional: false, schema: literalSchema };
|
|
848
779
|
}
|
|
849
780
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
// Handle union types
|
|
855
|
-
if (type.isUnion()) {
|
|
856
|
-
const types = type.types;
|
|
857
|
-
|
|
858
|
-
// Separate null, undefined, and other types
|
|
859
|
-
const hasNull = types.some(t => t.flags & ts.TypeFlags.Null);
|
|
860
|
-
const hasUndefined = types.some(t => t.flags & ts.TypeFlags.Undefined);
|
|
861
|
-
const nonNullUndefinedTypes = types.filter(
|
|
862
|
-
t => !(t.flags & ts.TypeFlags.Null) && !(t.flags & ts.TypeFlags.Undefined)
|
|
863
|
-
);
|
|
864
|
-
|
|
865
|
-
// Check if it's a string literal union (enum) - possibly with null/undefined
|
|
866
|
-
const stringLiterals = nonNullUndefinedTypes
|
|
867
|
-
.map(t => getStringLiteralValue(t))
|
|
868
|
-
.filter((v): v is string => v !== null);
|
|
869
|
-
|
|
870
|
-
// If all non-null/undefined types are string literals, use enum
|
|
871
|
-
if (stringLiterals.length > 0 && stringLiterals.length === nonNullUndefinedTypes.length) {
|
|
872
|
-
const enumSchema: JsonSchemaProperty = { type: 'string', enum: stringLiterals };
|
|
873
|
-
|
|
874
|
-
// If null or undefined is also present, wrap in anyOf
|
|
875
|
-
if (hasNull || hasUndefined) {
|
|
876
|
-
return {
|
|
877
|
-
isOptional: false,
|
|
878
|
-
schema: { anyOf: [enumSchema, { type: 'null' }] }
|
|
879
|
-
};
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
return {
|
|
883
|
-
isOptional: false,
|
|
884
|
-
schema: enumSchema
|
|
885
|
-
};
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
// Handle optional (T | undefined) - make it optional instead of union
|
|
889
|
-
if (hasUndefined && nonNullUndefinedTypes.length === 1 && !hasNull) {
|
|
890
|
-
const result = serializeType(nonNullUndefinedTypes[0], checker, visited, depth + 1, defs, defsVisited);
|
|
891
|
-
return { isOptional: true, schema: result.schema };
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
// Build anyOf array for proper JSON Schema union
|
|
895
|
-
const anyOf: JsonSchemaProperty[] = [];
|
|
896
|
-
|
|
897
|
-
// Add null if present
|
|
898
|
-
if (hasNull) {
|
|
899
|
-
anyOf.push({ type: 'null' });
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
// Add undefined as null (JSON doesn't have undefined)
|
|
903
|
-
if (hasUndefined) {
|
|
904
|
-
anyOf.push({ type: 'null' });
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
// Process non-null/undefined types
|
|
908
|
-
for (const unionType of nonNullUndefinedTypes) {
|
|
909
|
-
// Check if it's a primitive type
|
|
910
|
-
if (unionType.flags & ts.TypeFlags.String) {
|
|
911
|
-
anyOf.push({ type: 'string' });
|
|
912
|
-
} else if (unionType.flags & ts.TypeFlags.Number) {
|
|
913
|
-
anyOf.push({ type: 'number' });
|
|
914
|
-
} else if (unionType.flags & ts.TypeFlags.Boolean) {
|
|
915
|
-
anyOf.push({ type: 'boolean' });
|
|
916
|
-
} else {
|
|
917
|
-
// Try literal types first (using helper function)
|
|
918
|
-
const literalSchema = serializeLiteralType(unionType);
|
|
919
|
-
if (literalSchema) {
|
|
920
|
-
anyOf.push(literalSchema);
|
|
921
|
-
} else if (checker.isArrayType(unionType)) {
|
|
922
|
-
// Handle array types
|
|
923
|
-
const typeArgs = (unionType as ts.TypeReference).typeArguments;
|
|
924
|
-
if (typeArgs && typeArgs.length > 0) {
|
|
925
|
-
const itemResult = serializeType(typeArgs[0], checker, visited, depth + 1, defs, defsVisited);
|
|
926
|
-
anyOf.push({ type: 'array', items: itemResult.schema });
|
|
927
|
-
} else {
|
|
928
|
-
anyOf.push({ type: 'array' });
|
|
929
|
-
}
|
|
930
|
-
} else {
|
|
931
|
-
// Try to serialize the type (handles objects, type references, etc.)
|
|
932
|
-
// This will recursively resolve type aliases and interfaces
|
|
933
|
-
try {
|
|
934
|
-
const refResult = serializeType(unionType, checker, visited, depth + 1, defs, defsVisited);
|
|
935
|
-
|
|
936
|
-
// Check if we got a valid schema (not just 'any' fallback)
|
|
937
|
-
if (!isFallbackAnySchema(refResult.schema, unionType, checker)) {
|
|
938
|
-
anyOf.push(refResult.schema);
|
|
939
|
-
} else {
|
|
940
|
-
// Try to resolve type reference before giving up
|
|
941
|
-
const resolvedType = resolveTypeReference(unionType, checker);
|
|
942
|
-
if (resolvedType) {
|
|
943
|
-
try {
|
|
944
|
-
const resolvedResult = serializeType(resolvedType, checker, visited, depth + 1, defs, defsVisited);
|
|
945
|
-
if (!isFallbackAnySchema(resolvedResult.schema, resolvedType, checker)) {
|
|
946
|
-
anyOf.push(resolvedResult.schema);
|
|
947
|
-
} else {
|
|
948
|
-
anyOf.push({}); // Empty schema = any value (JSON Schema draft 2020-12)
|
|
949
|
-
}
|
|
950
|
-
} catch (error) {
|
|
951
|
-
anyOf.push({}); // Empty schema = any value (JSON Schema draft 2020-12)
|
|
952
|
-
}
|
|
953
|
-
} else {
|
|
954
|
-
// It's a type reference we couldn't fully resolve - use empty schema (any value)
|
|
955
|
-
anyOf.push({}); // Empty schema = any value (JSON Schema draft 2020-12)
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
} catch (error) {
|
|
959
|
-
// Fallback for unresolvable types
|
|
960
|
-
const unionTypeString = checker.typeToString(unionType);
|
|
961
|
-
console.warn(`Warning: Could not serialize union type: ${unionTypeString}`, error);
|
|
962
|
-
anyOf.push({}); // Empty schema = any value (JSON Schema draft 2020-12)
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
// If we only have one type (plus null/undefined), simplify
|
|
969
|
-
if (anyOf.length === 1) {
|
|
970
|
-
return { isOptional: hasNull || hasUndefined, schema: anyOf[0] };
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
// If we have multiple types, use anyOf
|
|
974
|
-
if (anyOf.length > 1) {
|
|
975
|
-
// Remove duplicate null entries
|
|
976
|
-
const uniqueAnyOf = anyOf.filter((schema, index, self) => {
|
|
977
|
-
if (schema.type === 'null') {
|
|
978
|
-
return index === self.findIndex(s => s.type === 'null');
|
|
979
|
-
}
|
|
980
|
-
return true;
|
|
981
|
-
});
|
|
982
|
-
|
|
983
|
-
return {
|
|
984
|
-
isOptional: false,
|
|
985
|
-
schema: { anyOf: uniqueAnyOf }
|
|
986
|
-
};
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
// Fallback
|
|
990
|
-
return {
|
|
991
|
-
isOptional: false,
|
|
992
|
-
schema: {} // Empty schema = any value (JSON Schema draft 2020-12)
|
|
993
|
-
};
|
|
781
|
+
const typeInfo = getTypeInfoForGenerator(type, checker, lookupFile);
|
|
782
|
+
if (!typeInfo) {
|
|
783
|
+
// Anonymous/inline type - can't serialize with generator
|
|
784
|
+
return { isOptional: false, schema: {} }; // Empty schema = any value
|
|
994
785
|
}
|
|
995
786
|
|
|
996
|
-
//
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
const allRequired: string[] = [];
|
|
1001
|
-
|
|
1002
|
-
for (const intersectionType of types) {
|
|
1003
|
-
const result = serializeType(intersectionType, checker, visited, depth + 1, defs, defsVisited);
|
|
1004
|
-
if (result.schema.type === 'object' && result.schema.properties) {
|
|
1005
|
-
Object.assign(allProperties, result.schema.properties);
|
|
1006
|
-
if (result.schema.required) {
|
|
1007
|
-
allRequired.push(...result.schema.required);
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
if (Object.keys(allProperties).length > 0) {
|
|
1013
|
-
return {
|
|
1014
|
-
isOptional: false,
|
|
1015
|
-
schema: {
|
|
1016
|
-
type: 'object',
|
|
1017
|
-
properties: allProperties,
|
|
1018
|
-
required: Array.from(new Set(allRequired))
|
|
1019
|
-
}
|
|
1020
|
-
};
|
|
1021
|
-
}
|
|
787
|
+
// Check cache first
|
|
788
|
+
const cacheKey = `${typeInfo.sourceFile}:${typeInfo.typeName}`;
|
|
789
|
+
if (generatorCache.has(cacheKey)) {
|
|
790
|
+
return generatorCache.get(cacheKey)!;
|
|
1022
791
|
}
|
|
1023
792
|
|
|
1024
|
-
//
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
return {
|
|
1030
|
-
isOptional: false,
|
|
1031
|
-
schema: { type: 'array', items: itemResult.schema }
|
|
1032
|
-
};
|
|
1033
|
-
}
|
|
1034
|
-
return { isOptional: false, schema: { type: 'array' } };
|
|
793
|
+
// Use generator
|
|
794
|
+
const result = await serializeTypeWithGenerator(type, checker, program, configPath, defs);
|
|
795
|
+
if (result) {
|
|
796
|
+
generatorCache.set(cacheKey, result);
|
|
797
|
+
return result;
|
|
1035
798
|
}
|
|
1036
799
|
|
|
1037
|
-
//
|
|
1038
|
-
|
|
1039
|
-
if (typeId !== undefined) {
|
|
1040
|
-
visited.add(typeId);
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
const properties: Record<string, JsonSchemaProperty> = {};
|
|
1044
|
-
const required: string[] = [];
|
|
1045
|
-
const props = checker.getPropertiesOfType(type);
|
|
1046
|
-
|
|
1047
|
-
// Check if this is a named type that should go in $defs
|
|
1048
|
-
const typeName = defs ? getTypeNameForDefs(type, checker) : null;
|
|
1049
|
-
const shouldUseDefs = defs && typeName && props.length > 0;
|
|
1050
|
-
|
|
1051
|
-
if (shouldUseDefs && typeId !== undefined) {
|
|
1052
|
-
// Check if we're already building this definition
|
|
1053
|
-
if (defsVisited.has(typeId)) {
|
|
1054
|
-
// Circular reference - return $ref
|
|
1055
|
-
return { isOptional: false, schema: { $ref: `#/$defs/${typeName}` } };
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
// Check if definition already exists
|
|
1059
|
-
if (defs[typeName]) {
|
|
1060
|
-
return { isOptional: false, schema: { $ref: `#/$defs/${typeName}` } };
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
// Start building the definition
|
|
1064
|
-
defsVisited.add(typeId);
|
|
1065
|
-
// Create placeholder in defs (will be filled below)
|
|
1066
|
-
defs[typeName] = { type: 'object', properties: {}, required: [] };
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
if (props.length > 0) {
|
|
1070
|
-
for (const prop of props) {
|
|
1071
|
-
try {
|
|
1072
|
-
const propDeclaration = prop.valueDeclaration || prop.declarations?.[0];
|
|
1073
|
-
|
|
1074
|
-
if (!propDeclaration) {
|
|
1075
|
-
// Skip properties without declarations
|
|
1076
|
-
continue;
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
const propType = checker.getTypeOfSymbolAtLocation(prop, propDeclaration);
|
|
1080
|
-
const isOptional = (prop.flags & ts.SymbolFlags.Optional) !== 0;
|
|
1081
|
-
|
|
1082
|
-
const propResult = serializeType(propType, checker, visited, depth + 1, defs, defsVisited);
|
|
1083
|
-
properties[prop.name] = propResult.schema;
|
|
1084
|
-
|
|
1085
|
-
// Track required properties
|
|
1086
|
-
if (!isOptional && !propResult.isOptional) {
|
|
1087
|
-
required.push(prop.name);
|
|
1088
|
-
}
|
|
1089
|
-
} catch (error) {
|
|
1090
|
-
// Skip properties that can't be serialized
|
|
1091
|
-
console.warn(`Warning: Could not serialize property ${prop.name}`, error);
|
|
1092
|
-
}
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
// Extract index signatures ([key: string]: T)
|
|
1096
|
-
const indexSignatures = checker.getIndexInfosOfType(type);
|
|
1097
|
-
if (indexSignatures && indexSignatures.length > 0) {
|
|
1098
|
-
try {
|
|
1099
|
-
const indexInfo = indexSignatures[0];
|
|
1100
|
-
if (indexInfo.declaration) {
|
|
1101
|
-
const indexType = checker.getTypeAtLocation(indexInfo.declaration);
|
|
1102
|
-
const indexResult = serializeType(indexType, checker, visited, depth + 1, defs, defsVisited);
|
|
1103
|
-
// JSON Schema uses additionalProperties for index signatures
|
|
1104
|
-
if (indexResult.schema.type !== 'any' || indexResult.schema.properties) {
|
|
1105
|
-
// Only set if it's a meaningful type (not just 'any' fallback)
|
|
1106
|
-
if (!isFallbackAnySchema(indexResult.schema, indexType, checker)) {
|
|
1107
|
-
properties['[key: string]'] = indexResult.schema;
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1111
|
-
} catch (error) {
|
|
1112
|
-
// Index signature extraction failed, continue without it
|
|
1113
|
-
console.warn(`Warning: Could not extract index signature`, error);
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
const schema: JsonSchemaProperty = {
|
|
1118
|
-
type: 'object',
|
|
1119
|
-
properties
|
|
1120
|
-
};
|
|
1121
|
-
|
|
1122
|
-
if (required.length > 0) {
|
|
1123
|
-
schema.required = required;
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
// If we're building a definition, update it and return $ref
|
|
1127
|
-
if (shouldUseDefs && typeName && defs) {
|
|
1128
|
-
defs[typeName] = schema;
|
|
1129
|
-
defsVisited.delete(typeId!);
|
|
1130
|
-
return { isOptional: false, schema: { $ref: `#/$defs/${typeName}` } };
|
|
1131
|
-
}
|
|
1132
|
-
|
|
1133
|
-
return { isOptional: false, schema };
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
// Fallback - check type flags to determine if it's a valid JSON Schema type
|
|
1138
|
-
// Valid JSON Schema types: string, number, integer, boolean, object, array, null
|
|
1139
|
-
|
|
1140
|
-
// Check if it's an enum type (TypeScript enum)
|
|
1141
|
-
if (type.flags & ts.TypeFlags.Enum) {
|
|
1142
|
-
try {
|
|
1143
|
-
const symbol = type.getSymbol();
|
|
1144
|
-
if (symbol) {
|
|
1145
|
-
const enumMembers = checker.getPropertiesOfType(type);
|
|
1146
|
-
if (enumMembers.length > 0) {
|
|
1147
|
-
// Extract enum values - enum members are typically string or number literals
|
|
1148
|
-
const values: (string | number)[] = [];
|
|
1149
|
-
for (const member of enumMembers) {
|
|
1150
|
-
const memberDeclaration = member.valueDeclaration || member.declarations?.[0];
|
|
1151
|
-
if (memberDeclaration) {
|
|
1152
|
-
const memberType = checker.getTypeOfSymbolAtLocation(member, memberDeclaration);
|
|
1153
|
-
const strValue = getStringLiteralValue(memberType);
|
|
1154
|
-
if (strValue !== null) {
|
|
1155
|
-
values.push(strValue);
|
|
1156
|
-
} else {
|
|
1157
|
-
const numValue = getNumberLiteralValue(memberType);
|
|
1158
|
-
if (numValue !== null) {
|
|
1159
|
-
values.push(numValue);
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
if (values.length > 0) {
|
|
1165
|
-
// Check if all values are strings or all are numbers
|
|
1166
|
-
const allStrings = values.every(v => typeof v === 'string');
|
|
1167
|
-
const allNumbers = values.every(v => typeof v === 'number');
|
|
1168
|
-
if (allStrings) {
|
|
1169
|
-
return { isOptional: false, schema: { type: 'string', enum: values.filter((v): v is string => typeof v === 'string') } };
|
|
1170
|
-
} else if (allNumbers) {
|
|
1171
|
-
const numberValues = values.filter((v): v is number => typeof v === 'number');
|
|
1172
|
-
return { isOptional: false, schema: { type: 'number', enum: numberValues } };
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
} catch (error) {
|
|
1178
|
-
console.warn(`Warning: Could not extract enum values for type: ${typeString}`, error);
|
|
1179
|
-
}
|
|
1180
|
-
// If we can't extract enum values, use empty schema (any value)
|
|
1181
|
-
return { isOptional: false, schema: {} }; // Empty schema = any value (JSON Schema draft 2020-12)
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
// Check if it's a type reference (like InternalFilterType.StringFilter)
|
|
1185
|
-
// Type references that aren't objects/arrays/primitives should use 'any'
|
|
1186
|
-
if (type.flags & ts.TypeFlags.Object) {
|
|
1187
|
-
// Already handled above, but check if it's an empty object type
|
|
1188
|
-
const props = checker.getPropertiesOfType(type);
|
|
1189
|
-
if (props.length === 0) {
|
|
1190
|
-
// Empty object or type reference we can't resolve
|
|
1191
|
-
// Check if typeString suggests it's a named type reference
|
|
1192
|
-
if (typeString && typeString.includes('.')) {
|
|
1193
|
-
return { isOptional: false, schema: {} }; // Empty schema = any value (JSON Schema draft 2020-12)
|
|
1194
|
-
}
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
// Check if typeString is a valid JSON Schema primitive type
|
|
1199
|
-
const lowerTypeString = typeString?.toLowerCase();
|
|
1200
|
-
|
|
1201
|
-
// If it's a valid JSON Schema type, use it (shouldn't happen here, but safety check)
|
|
1202
|
-
if (lowerTypeString && VALID_JSON_SCHEMA_TYPES.has(lowerTypeString)) {
|
|
1203
|
-
// Type assertion is safe here because VALID_JSON_SCHEMA_TYPES validates the value
|
|
1204
|
-
const validType = lowerTypeString as 'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array' | 'null';
|
|
1205
|
-
return {
|
|
1206
|
-
isOptional: false,
|
|
1207
|
-
schema: { type: validType }
|
|
1208
|
-
};
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
// Try to resolve type references before giving up
|
|
1212
|
-
const resolvedType = resolveTypeReference(type, checker);
|
|
1213
|
-
if (resolvedType) {
|
|
1214
|
-
try {
|
|
1215
|
-
const resolvedResult = serializeType(resolvedType, checker, visited, depth + 1, defs, defsVisited);
|
|
1216
|
-
if (!isFallbackAnySchema(resolvedResult.schema, resolvedType, checker)) {
|
|
1217
|
-
return resolvedResult;
|
|
1218
|
-
}
|
|
1219
|
-
} catch (error) {
|
|
1220
|
-
// Resolution failed, continue to fallback
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
// Handle special type flags
|
|
1225
|
-
if (type.flags & ts.TypeFlags.Never) {
|
|
1226
|
-
return { isOptional: false, schema: { type: 'null' } };
|
|
1227
|
-
}
|
|
1228
|
-
if (type.flags & ts.TypeFlags.Unknown) {
|
|
1229
|
-
return { isOptional: false, schema: {} }; // Empty schema = any value (JSON Schema draft 2020-12)
|
|
1230
|
-
}
|
|
1231
|
-
if (type.flags & ts.TypeFlags.Any) {
|
|
1232
|
-
return { isOptional: false, schema: {} }; // Empty schema = any value (JSON Schema draft 2020-12)
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
// For any other unrecognized type, use empty schema instead of invalid type strings
|
|
1236
|
-
// This prevents errors like "type": "InternalFilterType.StringFilter"
|
|
1237
|
-
// Empty schema {} means "allow any value" in JSON Schema draft 2020-12
|
|
1238
|
-
return {
|
|
1239
|
-
isOptional: false,
|
|
1240
|
-
schema: {} // Empty schema = any value (JSON Schema draft 2020-12)
|
|
1241
|
-
};
|
|
800
|
+
// Generator failed - return empty schema
|
|
801
|
+
return { isOptional: false, schema: {} }; // Empty schema = any value
|
|
1242
802
|
}
|
|
1243
803
|
|
|
1244
804
|
/**
|
|
@@ -1310,11 +870,13 @@ function extractDefaultValue(
|
|
|
1310
870
|
/**
|
|
1311
871
|
* Extract metadata from a function
|
|
1312
872
|
*/
|
|
1313
|
-
function extractFunctionMetadata(
|
|
873
|
+
async function extractFunctionMetadata(
|
|
1314
874
|
node: ts.FunctionDeclaration | ts.VariableDeclaration,
|
|
1315
875
|
checker: ts.TypeChecker,
|
|
1316
|
-
sourceFile: ts.SourceFile
|
|
1317
|
-
|
|
876
|
+
sourceFile: ts.SourceFile,
|
|
877
|
+
program?: ts.Program,
|
|
878
|
+
configPath?: string
|
|
879
|
+
): Promise<FunctionMetadata | null> {
|
|
1318
880
|
let functionNode: ts.FunctionDeclaration | ts.ArrowFunction | ts.FunctionExpression | null = null;
|
|
1319
881
|
let functionName: string;
|
|
1320
882
|
|
|
@@ -1435,10 +997,10 @@ function extractFunctionMetadata(
|
|
|
1435
997
|
}
|
|
1436
998
|
}
|
|
1437
999
|
|
|
1438
|
-
// Use
|
|
1439
|
-
//
|
|
1440
|
-
|
|
1441
|
-
const result = serializeType(type, checker,
|
|
1000
|
+
// Use ts-json-schema-generator for named types only
|
|
1001
|
+
// Anonymous types are not supported - agents can't use them anyway
|
|
1002
|
+
// Pass sourceFile to help resolve anonymous types to named types
|
|
1003
|
+
const result = await serializeType(type, checker, program, configPath, defs, sourceFile);
|
|
1442
1004
|
propSchema = result.schema;
|
|
1443
1005
|
isOptional = result.isOptional;
|
|
1444
1006
|
} else {
|
|
@@ -1579,7 +1141,7 @@ async function autoDiscoverAndExtract(projectRoot: string, outputPath: string) {
|
|
|
1579
1141
|
if (sourceFile) {
|
|
1580
1142
|
const result = findFunctionDefinition(toolName, sourceFile, program);
|
|
1581
1143
|
if (result) {
|
|
1582
|
-
const metadata = extractFunctionMetadata(result.node, checker, result.sourceFile);
|
|
1144
|
+
const metadata = await extractFunctionMetadata(result.node, checker, result.sourceFile, program, configPath);
|
|
1583
1145
|
if (metadata) {
|
|
1584
1146
|
functionsMap[toolName] = metadata;
|
|
1585
1147
|
discoveredFrom.push(path.relative(projectRoot, result.sourceFile.fileName));
|