@arcteninc/core 0.0.47 → 0.0.49
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 +1 -1
- package/scripts/cli-extract-types-auto.ts +236 -10
package/package.json
CHANGED
|
@@ -21,9 +21,11 @@ async function ensureGeneratorLoaded(): Promise<boolean> {
|
|
|
21
21
|
// @ts-ignore - ts-json-schema-generator is optional
|
|
22
22
|
tsJsonSchemaGeneratorModule = await import('ts-json-schema-generator');
|
|
23
23
|
generatorAvailable = true;
|
|
24
|
+
console.log('✓ ts-json-schema-generator loaded successfully');
|
|
24
25
|
return true;
|
|
25
26
|
} catch (error) {
|
|
26
27
|
tsJsonSchemaGeneratorModule = false; // Mark as failed
|
|
28
|
+
console.warn('⚠️ ts-json-schema-generator not available:', error);
|
|
27
29
|
return false;
|
|
28
30
|
}
|
|
29
31
|
}
|
|
@@ -574,6 +576,7 @@ function resolveImportPath(
|
|
|
574
576
|
|
|
575
577
|
/**
|
|
576
578
|
* Try to get type name and source file for use with ts-json-schema-generator
|
|
579
|
+
* Handles interfaces, type aliases, classes, and enums
|
|
577
580
|
*/
|
|
578
581
|
function getTypeInfoForGenerator(
|
|
579
582
|
type: ts.Type,
|
|
@@ -588,12 +591,13 @@ function getTypeInfoForGenerator(
|
|
|
588
591
|
const declarations = symbol.getDeclarations();
|
|
589
592
|
if (!declarations || declarations.length === 0) return null;
|
|
590
593
|
|
|
591
|
-
// Find the first declaration that's a type alias, interface, or
|
|
594
|
+
// Find the first declaration that's a type alias, interface, class, or enum
|
|
592
595
|
for (const decl of declarations) {
|
|
593
596
|
if (
|
|
594
597
|
ts.isTypeAliasDeclaration(decl) ||
|
|
595
598
|
ts.isInterfaceDeclaration(decl) ||
|
|
596
|
-
ts.isClassDeclaration(decl)
|
|
599
|
+
ts.isClassDeclaration(decl) ||
|
|
600
|
+
ts.isEnumDeclaration(decl)
|
|
597
601
|
) {
|
|
598
602
|
const sourceFile = decl.getSourceFile();
|
|
599
603
|
if (sourceFile) {
|
|
@@ -662,7 +666,8 @@ async function serializeTypeWithGenerator(
|
|
|
662
666
|
schema: schemaWithoutDefs as JsonSchemaProperty,
|
|
663
667
|
};
|
|
664
668
|
} catch (error) {
|
|
665
|
-
// If generator fails,
|
|
669
|
+
// If generator fails, log the error for debugging
|
|
670
|
+
console.warn(`⚠️ ts-json-schema-generator failed for type "${typeInfo.typeName}" from ${typeInfo.sourceFile}:`, error);
|
|
666
671
|
return null;
|
|
667
672
|
}
|
|
668
673
|
}
|
|
@@ -670,10 +675,120 @@ async function serializeTypeWithGenerator(
|
|
|
670
675
|
// Cache for generator results to avoid repeated calls
|
|
671
676
|
const generatorCache = new Map<string, { isOptional: boolean; schema: JsonSchemaProperty }>();
|
|
672
677
|
|
|
678
|
+
/**
|
|
679
|
+
* Custom serializer for anonymous types, enums, and object types
|
|
680
|
+
* This is a fallback when ts-json-schema-generator can't handle the type
|
|
681
|
+
*/
|
|
682
|
+
function serializeTypeCustom(
|
|
683
|
+
type: ts.Type,
|
|
684
|
+
checker: ts.TypeChecker,
|
|
685
|
+
defs?: Record<string, JsonSchemaProperty>,
|
|
686
|
+
visited = new Set<number>(),
|
|
687
|
+
depth = 0
|
|
688
|
+
): { isOptional: boolean; schema: JsonSchemaProperty } {
|
|
689
|
+
// Prevent infinite recursion
|
|
690
|
+
if (depth > 10) {
|
|
691
|
+
return { isOptional: false, schema: {} };
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
const typeId = (type as any).id;
|
|
695
|
+
if (typeId !== undefined && visited.has(typeId)) {
|
|
696
|
+
return { isOptional: false, schema: {} };
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
if (typeId !== undefined) {
|
|
700
|
+
visited.add(typeId);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Handle primitives first (safety net in case serializeType didn't catch them)
|
|
704
|
+
if (type.flags & ts.TypeFlags.String) {
|
|
705
|
+
return { isOptional: false, schema: { type: 'string' } };
|
|
706
|
+
}
|
|
707
|
+
if (type.flags & ts.TypeFlags.Number) {
|
|
708
|
+
return { isOptional: false, schema: { type: 'number' } };
|
|
709
|
+
}
|
|
710
|
+
if (type.flags & ts.TypeFlags.Boolean) {
|
|
711
|
+
return { isOptional: false, schema: { type: 'boolean' } };
|
|
712
|
+
}
|
|
713
|
+
if (type.flags & ts.TypeFlags.Null) {
|
|
714
|
+
return { isOptional: false, schema: { type: 'null' } };
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Handle string/number literals (enum values are often represented as literals)
|
|
718
|
+
if (type.flags & ts.TypeFlags.StringLiteral) {
|
|
719
|
+
const literalType = type as ts.StringLiteralType;
|
|
720
|
+
return { isOptional: false, schema: { type: 'string', const: literalType.value } };
|
|
721
|
+
}
|
|
722
|
+
if (type.flags & ts.TypeFlags.NumberLiteral) {
|
|
723
|
+
const literalType = type as ts.NumberLiteralType;
|
|
724
|
+
return { isOptional: false, schema: { type: 'number', const: literalType.value } };
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Handle enums (TypeScript enum types)
|
|
728
|
+
const symbol = type.getSymbol();
|
|
729
|
+
if (symbol) {
|
|
730
|
+
const declarations = symbol.getDeclarations();
|
|
731
|
+
if (declarations && declarations.length > 0) {
|
|
732
|
+
const firstDecl = declarations[0];
|
|
733
|
+
if (ts.isEnumDeclaration(firstDecl)) {
|
|
734
|
+
const enumMembers = firstDecl.members.map(m => {
|
|
735
|
+
if (m.initializer && ts.isStringLiteral(m.initializer)) {
|
|
736
|
+
return m.initializer.text;
|
|
737
|
+
} else if (m.initializer && ts.isNumericLiteral(m.initializer)) {
|
|
738
|
+
return Number(m.initializer.text);
|
|
739
|
+
} else if (m.name && ts.isIdentifier(m.name)) {
|
|
740
|
+
// For enum members without initializers, use the name
|
|
741
|
+
return m.name.text;
|
|
742
|
+
}
|
|
743
|
+
return null;
|
|
744
|
+
}).filter((v): v is string | number => v !== null);
|
|
745
|
+
|
|
746
|
+
if (enumMembers.length > 0) {
|
|
747
|
+
return { isOptional: false, schema: { enum: enumMembers } };
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// Handle object types with properties
|
|
754
|
+
if (type.flags & ts.TypeFlags.Object) {
|
|
755
|
+
const props = checker.getPropertiesOfType(type);
|
|
756
|
+
if (props.length > 0) {
|
|
757
|
+
const properties: Record<string, JsonSchemaProperty> = {};
|
|
758
|
+
const required: string[] = [];
|
|
759
|
+
|
|
760
|
+
for (const prop of props) {
|
|
761
|
+
const propName = prop.getName();
|
|
762
|
+
const propType = checker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration || prop.declarations?.[0] || checker.getDeclaredTypeOfSymbol(prop).symbol?.valueDeclaration || null as any);
|
|
763
|
+
const isOptional = (prop.flags & ts.SymbolFlags.Optional) !== 0;
|
|
764
|
+
|
|
765
|
+
// Recursively serialize the property type
|
|
766
|
+
const propResult = serializeTypeCustom(propType, checker, defs, new Set(visited), depth + 1);
|
|
767
|
+
properties[propName] = propResult.schema;
|
|
768
|
+
|
|
769
|
+
if (!isOptional && !propResult.isOptional) {
|
|
770
|
+
required.push(propName);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
return {
|
|
775
|
+
isOptional: false,
|
|
776
|
+
schema: {
|
|
777
|
+
type: 'object',
|
|
778
|
+
properties,
|
|
779
|
+
...(required.length > 0 && { required })
|
|
780
|
+
}
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// Fallback to empty schema
|
|
786
|
+
return { isOptional: false, schema: {} };
|
|
787
|
+
}
|
|
788
|
+
|
|
673
789
|
/**
|
|
674
790
|
* Serialize a TypeScript type to JSON Schema format using ts-json-schema-generator
|
|
675
|
-
*
|
|
676
|
-
* Returns empty schema for anonymous types
|
|
791
|
+
* Handles primitives, arrays, and named types (interfaces, type aliases, classes, enums)
|
|
677
792
|
*/
|
|
678
793
|
async function serializeType(
|
|
679
794
|
type: ts.Type,
|
|
@@ -682,14 +797,124 @@ async function serializeType(
|
|
|
682
797
|
configPath: string | undefined,
|
|
683
798
|
defs?: Record<string, JsonSchemaProperty>
|
|
684
799
|
): Promise<{ isOptional: boolean; schema: JsonSchemaProperty }> {
|
|
800
|
+
// Handle primitives first (these don't need the generator)
|
|
801
|
+
if (type.flags & ts.TypeFlags.String) {
|
|
802
|
+
return { isOptional: false, schema: { type: 'string' } };
|
|
803
|
+
}
|
|
804
|
+
if (type.flags & ts.TypeFlags.Number) {
|
|
805
|
+
return { isOptional: false, schema: { type: 'number' } };
|
|
806
|
+
}
|
|
807
|
+
if (type.flags & ts.TypeFlags.Boolean) {
|
|
808
|
+
return { isOptional: false, schema: { type: 'boolean' } };
|
|
809
|
+
}
|
|
810
|
+
if (type.flags & ts.TypeFlags.Null) {
|
|
811
|
+
return { isOptional: false, schema: { type: 'null' } };
|
|
812
|
+
}
|
|
813
|
+
if (type.flags & ts.TypeFlags.Undefined || type.flags & ts.TypeFlags.Void) {
|
|
814
|
+
return { isOptional: true, schema: { type: 'null' } };
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// Handle string/number literals (enum values are often represented as literals)
|
|
818
|
+
if (type.flags & ts.TypeFlags.StringLiteral) {
|
|
819
|
+
const literalType = type as ts.StringLiteralType;
|
|
820
|
+
return { isOptional: false, schema: { type: 'string', const: literalType.value } };
|
|
821
|
+
}
|
|
822
|
+
if (type.flags & ts.TypeFlags.NumberLiteral) {
|
|
823
|
+
const literalType = type as ts.NumberLiteralType;
|
|
824
|
+
return { isOptional: false, schema: { type: 'number', const: literalType.value } };
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// Handle arrays
|
|
828
|
+
if (checker.isArrayType(type)) {
|
|
829
|
+
const typeArgs = (type as ts.TypeReference).typeArguments;
|
|
830
|
+
if (typeArgs && typeArgs.length > 0) {
|
|
831
|
+
const itemResult = await serializeType(typeArgs[0], checker, program, configPath, defs);
|
|
832
|
+
return {
|
|
833
|
+
isOptional: false,
|
|
834
|
+
schema: { type: 'array', items: itemResult.schema }
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
return { isOptional: false, schema: { type: 'array' } };
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// Handle union types (like string | null)
|
|
841
|
+
if (type.isUnion()) {
|
|
842
|
+
const types = type.types;
|
|
843
|
+
const hasNull = types.some(t => t.flags & ts.TypeFlags.Null);
|
|
844
|
+
const hasUndefined = types.some(t => t.flags & ts.TypeFlags.Undefined);
|
|
845
|
+
const nonNullUndefinedTypes = types.filter(
|
|
846
|
+
t => !(t.flags & ts.TypeFlags.Null) && !(t.flags & ts.TypeFlags.Undefined)
|
|
847
|
+
);
|
|
848
|
+
|
|
849
|
+
// If it's just T | undefined, make it optional
|
|
850
|
+
if (hasUndefined && nonNullUndefinedTypes.length === 1 && !hasNull) {
|
|
851
|
+
const result = await serializeType(nonNullUndefinedTypes[0], checker, program, configPath, defs);
|
|
852
|
+
return { isOptional: true, schema: result.schema };
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// Check if union is all string/number literals (enum-like union)
|
|
856
|
+
// This handles cases where TypeScript represents enums as unions of literals
|
|
857
|
+
const allStringLiterals = nonNullUndefinedTypes.every(t => t.flags & ts.TypeFlags.StringLiteral);
|
|
858
|
+
const allNumberLiterals = nonNullUndefinedTypes.every(t => t.flags & ts.TypeFlags.NumberLiteral);
|
|
859
|
+
|
|
860
|
+
if (allStringLiterals && nonNullUndefinedTypes.length > 0) {
|
|
861
|
+
const enumValues = nonNullUndefinedTypes.map(t => {
|
|
862
|
+
const literalType = t as ts.StringLiteralType;
|
|
863
|
+
return literalType.value;
|
|
864
|
+
});
|
|
865
|
+
const schema: JsonSchemaProperty = { enum: enumValues };
|
|
866
|
+
return { isOptional: hasNull || hasUndefined, schema };
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
if (allNumberLiterals && nonNullUndefinedTypes.length > 0) {
|
|
870
|
+
const enumValues = nonNullUndefinedTypes.map(t => {
|
|
871
|
+
const literalType = t as ts.NumberLiteralType;
|
|
872
|
+
return literalType.value;
|
|
873
|
+
});
|
|
874
|
+
const schema: JsonSchemaProperty = { enum: enumValues };
|
|
875
|
+
return { isOptional: hasNull || hasUndefined, schema };
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// Build anyOf for unions
|
|
879
|
+
if (nonNullUndefinedTypes.length > 0 || hasNull) {
|
|
880
|
+
const anyOf: JsonSchemaProperty[] = [];
|
|
881
|
+
if (hasNull || hasUndefined) {
|
|
882
|
+
anyOf.push({ type: 'null' });
|
|
883
|
+
}
|
|
884
|
+
for (const unionType of nonNullUndefinedTypes) {
|
|
885
|
+
const result = await serializeType(unionType, checker, program, configPath, defs);
|
|
886
|
+
anyOf.push(result.schema);
|
|
887
|
+
}
|
|
888
|
+
if (anyOf.length === 1) {
|
|
889
|
+
return { isOptional: hasNull || hasUndefined, schema: anyOf[0] };
|
|
890
|
+
}
|
|
891
|
+
return { isOptional: false, schema: { anyOf } };
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// Check if it's an enum - handle with custom serializer first
|
|
896
|
+
const symbol = type.getSymbol();
|
|
897
|
+
if (symbol) {
|
|
898
|
+
const declarations = symbol.getDeclarations();
|
|
899
|
+
if (declarations && declarations.length > 0) {
|
|
900
|
+
const firstDecl = declarations[0];
|
|
901
|
+
if (ts.isEnumDeclaration(firstDecl)) {
|
|
902
|
+
// Use custom serializer for enums (more reliable)
|
|
903
|
+
return serializeTypeCustom(type, checker, defs);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
// For named types (interfaces, type aliases, classes), try generator first
|
|
685
909
|
if (!program || !configPath) {
|
|
686
|
-
|
|
910
|
+
// No program/config - use custom serializer
|
|
911
|
+
return serializeTypeCustom(type, checker, defs);
|
|
687
912
|
}
|
|
688
913
|
|
|
689
914
|
const typeInfo = getTypeInfoForGenerator(type, checker);
|
|
690
915
|
if (!typeInfo) {
|
|
691
|
-
// Anonymous/inline type -
|
|
692
|
-
return
|
|
916
|
+
// Anonymous/inline type - use custom serializer
|
|
917
|
+
return serializeTypeCustom(type, checker, defs);
|
|
693
918
|
}
|
|
694
919
|
|
|
695
920
|
// Check cache first
|
|
@@ -698,14 +923,15 @@ async function serializeType(
|
|
|
698
923
|
return generatorCache.get(cacheKey)!;
|
|
699
924
|
}
|
|
700
925
|
|
|
701
|
-
// Use generator
|
|
926
|
+
// Use generator for named types
|
|
702
927
|
const result = await serializeTypeWithGenerator(type, checker, program, configPath, defs);
|
|
703
928
|
if (result) {
|
|
704
929
|
generatorCache.set(cacheKey, result);
|
|
705
930
|
return result;
|
|
706
931
|
}
|
|
707
932
|
|
|
708
|
-
// Generator failed - return empty schema
|
|
933
|
+
// Generator failed - log and return empty schema
|
|
934
|
+
console.warn(`⚠️ Failed to generate schema for type "${typeInfo.typeName}" from ${typeInfo.sourceFile}. Using empty schema.`);
|
|
709
935
|
return { isOptional: false, schema: {} }; // Empty schema = any value
|
|
710
936
|
}
|
|
711
937
|
|