@kinotic-ai/kinotic-cli 1.0.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/README.md +594 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +6 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +6 -0
- package/dist/commands/generate.d.ts +11 -0
- package/dist/commands/generate.js +36 -0
- package/dist/commands/initialize.d.ts +12 -0
- package/dist/commands/initialize.js +102 -0
- package/dist/commands/synchronize.d.ts +17 -0
- package/dist/commands/synchronize.js +154 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/internal/CodeGenerationService.d.ts +24 -0
- package/dist/internal/CodeGenerationService.js +256 -0
- package/dist/internal/Logger.d.ts +27 -0
- package/dist/internal/Logger.js +35 -0
- package/dist/internal/ProjectMigrationService.d.ts +28 -0
- package/dist/internal/ProjectMigrationService.js +99 -0
- package/dist/internal/Utils.d.ts +66 -0
- package/dist/internal/Utils.js +349 -0
- package/dist/internal/converter/ConverterConstants.d.ts +3 -0
- package/dist/internal/converter/ConverterConstants.js +4 -0
- package/dist/internal/converter/DefaultConversionContext.d.ts +29 -0
- package/dist/internal/converter/DefaultConversionContext.js +112 -0
- package/dist/internal/converter/IConversionContext.d.ts +64 -0
- package/dist/internal/converter/IConversionContext.js +11 -0
- package/dist/internal/converter/IConverterStrategy.d.ts +35 -0
- package/dist/internal/converter/IConverterStrategy.js +1 -0
- package/dist/internal/converter/ITypeConverter.d.ts +28 -0
- package/dist/internal/converter/ITypeConverter.js +1 -0
- package/dist/internal/converter/SpecificTypesConverter.d.ts +12 -0
- package/dist/internal/converter/SpecificTypesConverter.js +24 -0
- package/dist/internal/converter/codegen/ArrayC3TypeToStatementMapper.d.ts +9 -0
- package/dist/internal/converter/codegen/ArrayC3TypeToStatementMapper.js +46 -0
- package/dist/internal/converter/codegen/ObjectC3TypeToStatementMapper.d.ts +13 -0
- package/dist/internal/converter/codegen/ObjectC3TypeToStatementMapper.js +67 -0
- package/dist/internal/converter/codegen/PrimitiveC3TypeToStatementMapper.d.ts +9 -0
- package/dist/internal/converter/codegen/PrimitiveC3TypeToStatementMapper.js +24 -0
- package/dist/internal/converter/codegen/StatementMapper.d.ts +40 -0
- package/dist/internal/converter/codegen/StatementMapper.js +132 -0
- package/dist/internal/converter/codegen/StatementMapperConversionState.d.ts +9 -0
- package/dist/internal/converter/codegen/StatementMapperConversionState.js +17 -0
- package/dist/internal/converter/codegen/StatementMapperConverterStrategy.d.ts +16 -0
- package/dist/internal/converter/codegen/StatementMapperConverterStrategy.js +30 -0
- package/dist/internal/converter/codegen/UnionC3TypeToStatementMapper.d.ts +9 -0
- package/dist/internal/converter/codegen/UnionC3TypeToStatementMapper.js +46 -0
- package/dist/internal/converter/common/BaseConversionState.d.ts +9 -0
- package/dist/internal/converter/common/BaseConversionState.js +13 -0
- package/dist/internal/converter/typescript/ArrayToC3Type.d.ts +9 -0
- package/dist/internal/converter/typescript/ArrayToC3Type.js +17 -0
- package/dist/internal/converter/typescript/ConverterUtils.d.ts +4 -0
- package/dist/internal/converter/typescript/ConverterUtils.js +261 -0
- package/dist/internal/converter/typescript/EnumToC3Type.d.ts +12 -0
- package/dist/internal/converter/typescript/EnumToC3Type.js +26 -0
- package/dist/internal/converter/typescript/ObjectLikeToC3Type.d.ts +15 -0
- package/dist/internal/converter/typescript/ObjectLikeToC3Type.js +111 -0
- package/dist/internal/converter/typescript/PrimitiveToC3Type.d.ts +10 -0
- package/dist/internal/converter/typescript/PrimitiveToC3Type.js +33 -0
- package/dist/internal/converter/typescript/QueryOptionsToC3Type.d.ts +9 -0
- package/dist/internal/converter/typescript/QueryOptionsToC3Type.js +9 -0
- package/dist/internal/converter/typescript/TenantSelectionToC3Type.d.ts +9 -0
- package/dist/internal/converter/typescript/TenantSelectionToC3Type.js +9 -0
- package/dist/internal/converter/typescript/TypescriptConversionState.d.ts +24 -0
- package/dist/internal/converter/typescript/TypescriptConversionState.js +26 -0
- package/dist/internal/converter/typescript/TypescriptConverterStrategy.d.ts +16 -0
- package/dist/internal/converter/typescript/TypescriptConverterStrategy.js +44 -0
- package/dist/internal/converter/typescript/UnionToC3Type.d.ts +15 -0
- package/dist/internal/converter/typescript/UnionToC3Type.js +184 -0
- package/dist/internal/state/Environment.d.ts +13 -0
- package/dist/internal/state/Environment.js +65 -0
- package/dist/internal/state/IStateManager.d.ts +19 -0
- package/dist/internal/state/IStateManager.js +41 -0
- package/dist/internal/state/KinoticProjectConfigUtil.d.ts +9 -0
- package/dist/internal/state/KinoticProjectConfigUtil.js +153 -0
- package/dist/templates/AdminEntityService.liquid +14 -0
- package/dist/templates/BaseAdminEntityService.liquid +19 -0
- package/dist/templates/BaseEntityService.liquid +48 -0
- package/dist/templates/EntityService.liquid +14 -0
- package/dist/templates/KinoticProjectConfig.ts.liquid +10 -0
- package/oclif.manifest.json +161 -0
- package/package.json +97 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { BaseConversionState } from '../common/BaseConversionState.js';
|
|
2
|
+
/**
|
|
3
|
+
* The state of the Typescript to C3Type conversion process.
|
|
4
|
+
*/
|
|
5
|
+
export class TypescriptConversionState extends BaseConversionState {
|
|
6
|
+
/**
|
|
7
|
+
* Contains a stack of object names for objects being processed.
|
|
8
|
+
*/
|
|
9
|
+
objectNameStack = [];
|
|
10
|
+
/**
|
|
11
|
+
* Contains a stack of property names for any properties processed that are a union type.
|
|
12
|
+
*/
|
|
13
|
+
unionPropertyNameStack = [];
|
|
14
|
+
/**
|
|
15
|
+
* Boolean to indicate if the source path should be added to the metadata for the {@link C3Type}s created.
|
|
16
|
+
*/
|
|
17
|
+
shouldAddSourcePathToMetadata = true;
|
|
18
|
+
/**
|
|
19
|
+
* Boolean to indicate if multi-tenant selection is enabled.
|
|
20
|
+
* This is done by adding a {@link TenantId} decorator to the entity.
|
|
21
|
+
*/
|
|
22
|
+
multiTenantSelectionEnabled = false;
|
|
23
|
+
constructor(application) {
|
|
24
|
+
super(application);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { C3Type } from '@kinotic-ai/idl';
|
|
2
|
+
import { Type } from 'ts-morph';
|
|
3
|
+
import { IConverterStrategy } from '../IConverterStrategy.js';
|
|
4
|
+
import { Logger } from '../../Logger.js';
|
|
5
|
+
import { TypescriptConversionState } from './TypescriptConversionState.js';
|
|
6
|
+
import { ITypeConverter } from '../ITypeConverter.js';
|
|
7
|
+
export declare class TypescriptConverterStrategy implements IConverterStrategy<Type, C3Type, TypescriptConversionState> {
|
|
8
|
+
private readonly _initialState;
|
|
9
|
+
private readonly _logger;
|
|
10
|
+
private readonly _typeConverters;
|
|
11
|
+
constructor(initialState: (() => TypescriptConversionState) | TypescriptConversionState, logger: Logger);
|
|
12
|
+
initialState(): (() => TypescriptConversionState) | TypescriptConversionState;
|
|
13
|
+
logger(): Logger | (() => Logger);
|
|
14
|
+
typeConverters(): Array<ITypeConverter<Type, C3Type, TypescriptConversionState>>;
|
|
15
|
+
valueToString(value: Type): string;
|
|
16
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { QueryOptionsToC3Type } from './QueryOptionsToC3Type.js';
|
|
2
|
+
import { TenantSelectionToC3Type } from './TenantSelectionToC3Type.js';
|
|
3
|
+
import { ObjectLikeToC3Type } from './ObjectLikeToC3Type.js';
|
|
4
|
+
import { PrimitiveToC3Type } from './PrimitiveToC3Type.js';
|
|
5
|
+
import { UnionToC3Type } from './UnionToC3Type.js';
|
|
6
|
+
import { ArrayToC3Type } from './ArrayToC3Type.js';
|
|
7
|
+
import { EnumToC3Type } from './EnumToC3Type.js';
|
|
8
|
+
export class TypescriptConverterStrategy {
|
|
9
|
+
_initialState;
|
|
10
|
+
_logger;
|
|
11
|
+
// CAUTION: The order here is important!
|
|
12
|
+
// TenantSelection is an array, but we need the specific type
|
|
13
|
+
// QueryOptions is an object, but we need the specific type
|
|
14
|
+
// Arrays are considered objects but objects are not arrays. So the Array converter must come before the "ObjectLike" converter
|
|
15
|
+
// Enums ar considered unions but unions are not enums. So the enum converter must come before the union converter
|
|
16
|
+
_typeConverters = [new PrimitiveToC3Type(),
|
|
17
|
+
new TenantSelectionToC3Type(),
|
|
18
|
+
new QueryOptionsToC3Type(),
|
|
19
|
+
new ArrayToC3Type(),
|
|
20
|
+
new ObjectLikeToC3Type(),
|
|
21
|
+
new EnumToC3Type(),
|
|
22
|
+
new UnionToC3Type()];
|
|
23
|
+
constructor(initialState, logger) {
|
|
24
|
+
this._initialState = initialState;
|
|
25
|
+
this._logger = logger;
|
|
26
|
+
}
|
|
27
|
+
initialState() {
|
|
28
|
+
return this._initialState;
|
|
29
|
+
}
|
|
30
|
+
logger() {
|
|
31
|
+
return this._logger;
|
|
32
|
+
}
|
|
33
|
+
typeConverters() {
|
|
34
|
+
return this._typeConverters;
|
|
35
|
+
}
|
|
36
|
+
valueToString(value) {
|
|
37
|
+
// TODO: work on making this as helpful for debugging as possible
|
|
38
|
+
let ret = value?.getSymbol()?.getValueDeclaration()?.print();
|
|
39
|
+
if (!ret) {
|
|
40
|
+
ret = value.getText();
|
|
41
|
+
}
|
|
42
|
+
return ret;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { C3Type } from '@kinotic-ai/idl';
|
|
2
|
+
import { Type } from 'ts-morph';
|
|
3
|
+
import { TypescriptConversionState } from './TypescriptConversionState.js';
|
|
4
|
+
import { ITypeConverter } from '../ITypeConverter.js';
|
|
5
|
+
import { IConversionContext } from '../IConversionContext.js';
|
|
6
|
+
/**
|
|
7
|
+
* Converts a typescript union type to a C3Type
|
|
8
|
+
*/
|
|
9
|
+
export declare class UnionToC3Type implements ITypeConverter<Type, C3Type, TypescriptConversionState> {
|
|
10
|
+
convert(value: Type, conversionContext: IConversionContext<Type, C3Type, TypescriptConversionState>): C3Type;
|
|
11
|
+
supports(value: Type): boolean;
|
|
12
|
+
private isPrimitive;
|
|
13
|
+
private getUnionC3TypeName;
|
|
14
|
+
private capitalizeFirstLetter;
|
|
15
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { BooleanC3Type, StringC3Type, UnionC3Type } from '@kinotic-ai/idl';
|
|
2
|
+
/**
|
|
3
|
+
* Converts a typescript union type to a C3Type
|
|
4
|
+
*/
|
|
5
|
+
export class UnionToC3Type {
|
|
6
|
+
convert(value, conversionContext) {
|
|
7
|
+
let ret;
|
|
8
|
+
const convertedList = [];
|
|
9
|
+
let primitiveCount = 0;
|
|
10
|
+
let arrayCount = 0;
|
|
11
|
+
let enumCount = 0;
|
|
12
|
+
let booleanLiteral = false;
|
|
13
|
+
const stringLiterals = [];
|
|
14
|
+
// Unions are interesting because there are unions that are not invalid but not a union when it comes to C3
|
|
15
|
+
// For example:
|
|
16
|
+
// string | undefined | null
|
|
17
|
+
// This is not a union in C3 because it is not a union of objects
|
|
18
|
+
// Another example:
|
|
19
|
+
// let var?: string
|
|
20
|
+
// Which provides the union types of string | undefined
|
|
21
|
+
// Another example:
|
|
22
|
+
// let var?: MyEnum
|
|
23
|
+
// Which provides the union types of undefined | MyEnum.FIRST | MyEnum.SECOND ect..
|
|
24
|
+
// Another example:
|
|
25
|
+
// let var?: boolean
|
|
26
|
+
// Which provides the union types of undefined | true | false
|
|
27
|
+
value.getUnionTypes().forEach((unionType) => {
|
|
28
|
+
if (!unionType.isUndefined() && !unionType.isNull()) {
|
|
29
|
+
// Array has to be checked before object because arrays are objects
|
|
30
|
+
if (unionType.isArray()) {
|
|
31
|
+
convertedList.push(conversionContext.convert(unionType));
|
|
32
|
+
arrayCount++;
|
|
33
|
+
}
|
|
34
|
+
else if (unionType.isStringLiteral()) { // This must be before is primitive because string literals are primitives
|
|
35
|
+
stringLiterals.push(unionType.getText());
|
|
36
|
+
}
|
|
37
|
+
else if (unionType.isBooleanLiteral()) { // This must be before is primitive because boolean literals are primitives
|
|
38
|
+
booleanLiteral = true;
|
|
39
|
+
}
|
|
40
|
+
else if (this.isPrimitive(unionType)) {
|
|
41
|
+
if (unionType.isLiteral()) {
|
|
42
|
+
convertedList.push(conversionContext.convert(unionType.getApparentType()));
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
convertedList.push(conversionContext.convert(unionType));
|
|
46
|
+
}
|
|
47
|
+
primitiveCount++;
|
|
48
|
+
}
|
|
49
|
+
else if (unionType.isObject()) { // This must be after is primitive because dates are objects
|
|
50
|
+
convertedList.push(conversionContext.convert(unionType));
|
|
51
|
+
}
|
|
52
|
+
else if (unionType.isEnumLiteral()) {
|
|
53
|
+
// Since the enums come back as a bunch of enum literals we only convert the first one we encounter
|
|
54
|
+
if (enumCount === 0) {
|
|
55
|
+
const toConvert = unionType.getSymbol()?.getValueDeclaration()?.getParent()?.getType();
|
|
56
|
+
if (toConvert) {
|
|
57
|
+
convertedList.push(conversionContext.convert(toConvert));
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
throw new Error("Could not find the parent type of the enum literal: " + unionType.getText());
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
enumCount++;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
throw new Error("Union type contains a type that is not supported: " + unionType.getText());
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
if (primitiveCount > 1) {
|
|
71
|
+
throw new Error("Union type contains more than one primitive type: " + value.getText());
|
|
72
|
+
}
|
|
73
|
+
if (arrayCount > 1) {
|
|
74
|
+
throw new Error("Union type contains more than one array type: " + value.getText());
|
|
75
|
+
}
|
|
76
|
+
// Now we check that a union is only objets or is single non object type
|
|
77
|
+
// Since kinotic does not support unions of primitives, arrays, or enums
|
|
78
|
+
if (primitiveCount === 1) {
|
|
79
|
+
if (convertedList.length === 1) {
|
|
80
|
+
ret = convertedList[0];
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
throw new Error("You cannot create a Union with a primitive type and other types: " + value.getText());
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else if (arrayCount === 1) {
|
|
87
|
+
if (convertedList.length === 1) {
|
|
88
|
+
ret = convertedList[0];
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
throw new Error("You cannot create a Union with an array type and other types: " + value.getText());
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else if (enumCount >= 1) {
|
|
95
|
+
if (convertedList.length === 1) {
|
|
96
|
+
ret = convertedList[0];
|
|
97
|
+
if (ret.values.length !== enumCount) {
|
|
98
|
+
throw new Error("There were more enums found in the union than were converted: " + value.getText() + "\n(Sorry if this error is kind of confusing, it is a bit of a edge case)");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
throw new Error("You cannot create a Union with an enum type and other types, or more than one enum type: " + value.getText());
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else if (booleanLiteral) {
|
|
106
|
+
if (primitiveCount > 0 || arrayCount > 0 || enumCount > 0 || convertedList.length > 0) {
|
|
107
|
+
throw new Error("You cannot create a Union with boolean and other types: " + value.getText());
|
|
108
|
+
}
|
|
109
|
+
ret = new BooleanC3Type();
|
|
110
|
+
}
|
|
111
|
+
else if (stringLiterals.length > 0) {
|
|
112
|
+
// This handles the case of a union of string literals
|
|
113
|
+
// eg: "first" | "second" | "third"
|
|
114
|
+
// This is a special case because we want to convert it to an enum
|
|
115
|
+
// but for now we will just return a string type
|
|
116
|
+
if (primitiveCount > 0 || arrayCount > 0 || enumCount > 0 || convertedList.length > 0) {
|
|
117
|
+
throw new Error("You cannot create a Union with string literals and other types: " + value.getText());
|
|
118
|
+
}
|
|
119
|
+
// const enumType = new EnumC3Type()
|
|
120
|
+
// enumType.namespace = conversionContext.state().namespace
|
|
121
|
+
// enumType.name = this.getUnionPropertyName(conversionContext)
|
|
122
|
+
// for (const stringLiteral of stringLiterals) {
|
|
123
|
+
// enumType.addValue(trim(stringLiteral, '"'))
|
|
124
|
+
// }
|
|
125
|
+
ret = new StringC3Type();
|
|
126
|
+
}
|
|
127
|
+
else if (convertedList.length === 1) { // In this case it was a single optional object, let myVar?: MyObject
|
|
128
|
+
ret = convertedList[0];
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
const namespace = conversionContext.state().application;
|
|
132
|
+
const name = this.getUnionC3TypeName(value, conversionContext);
|
|
133
|
+
const unionType = new UnionC3Type(name, namespace);
|
|
134
|
+
unionType.types = convertedList;
|
|
135
|
+
ret = unionType;
|
|
136
|
+
}
|
|
137
|
+
return ret;
|
|
138
|
+
}
|
|
139
|
+
supports(value) {
|
|
140
|
+
return value.isUnion();
|
|
141
|
+
}
|
|
142
|
+
isPrimitive(type) {
|
|
143
|
+
const typeText = type.getText().toLowerCase();
|
|
144
|
+
return typeText === 'string'
|
|
145
|
+
|| typeText === 'number'
|
|
146
|
+
|| typeText === 'boolean'
|
|
147
|
+
|| typeText === 'date'
|
|
148
|
+
|| (type.isLiteral() && this.isPrimitive(type.getApparentType()));
|
|
149
|
+
}
|
|
150
|
+
getUnionC3TypeName(value, conversionContext) {
|
|
151
|
+
let ret = null;
|
|
152
|
+
// first try to get name from type.
|
|
153
|
+
const parts = value.getText(undefined, 0).split('|');
|
|
154
|
+
if (parts.length == 1) {
|
|
155
|
+
// This is a single type, so we can use the name of the type
|
|
156
|
+
ret = parts[0].trim();
|
|
157
|
+
}
|
|
158
|
+
else if (parts.length == 2) {
|
|
159
|
+
// if 2 this can be something like UnionType | undefined, or like SomeType | AnotherType
|
|
160
|
+
// Make sure one of the strings is undefined, if not this is something like SomeType | AnotherType
|
|
161
|
+
if (parts[0].trim() === 'undefined') {
|
|
162
|
+
ret = parts[1].trim();
|
|
163
|
+
}
|
|
164
|
+
else if (parts[1].trim() === 'undefined') {
|
|
165
|
+
ret = parts[0].trim();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (!ret) {
|
|
169
|
+
// since we could not determine the type name above we can use the property name
|
|
170
|
+
if (conversionContext.state().unionPropertyNameStack.length > 0) {
|
|
171
|
+
const unionPropName = this.capitalizeFirstLetter(conversionContext.state().unionPropertyNameStack[conversionContext.state().unionPropertyNameStack.length - 1]);
|
|
172
|
+
const objectName = conversionContext.state().objectNameStack[conversionContext.state().objectNameStack.length - 1];
|
|
173
|
+
ret = objectName + '_' + unionPropName;
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
throw new Error("The current property name is not set in the conversion state");
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return ret;
|
|
180
|
+
}
|
|
181
|
+
capitalizeFirstLetter(s) {
|
|
182
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare class ServerConfiguration {
|
|
2
|
+
name: string;
|
|
3
|
+
url: string;
|
|
4
|
+
}
|
|
5
|
+
export declare class Environment {
|
|
6
|
+
servers: ServerConfiguration[];
|
|
7
|
+
defaultServer: ServerConfiguration;
|
|
8
|
+
hasServer(server: string): boolean;
|
|
9
|
+
findServer(server: string): ServerConfiguration | null;
|
|
10
|
+
}
|
|
11
|
+
export declare function loadEnvironment(dataDir: string): Promise<Environment>;
|
|
12
|
+
export declare function saveEnvironment(dataDir: string, environment: Environment): Promise<void>;
|
|
13
|
+
export declare function resolveServer(dataDir: string, server?: string | null): Promise<ServerConfiguration>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { createStateManager } from './IStateManager.js';
|
|
2
|
+
const ENVIRONMENT_KEY = 'kinotic-environment';
|
|
3
|
+
export class ServerConfiguration {
|
|
4
|
+
name;
|
|
5
|
+
url;
|
|
6
|
+
}
|
|
7
|
+
export class Environment {
|
|
8
|
+
servers = [];
|
|
9
|
+
defaultServer;
|
|
10
|
+
hasServer(server) {
|
|
11
|
+
return this.findServer(server) !== null;
|
|
12
|
+
}
|
|
13
|
+
findServer(server) {
|
|
14
|
+
let ret = null;
|
|
15
|
+
for (const serverConfig of this.servers) {
|
|
16
|
+
if (serverConfig.name === server
|
|
17
|
+
|| serverConfig.url === server) {
|
|
18
|
+
ret = serverConfig;
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return ret;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export async function loadEnvironment(dataDir) {
|
|
26
|
+
const stateManager = createStateManager(dataDir);
|
|
27
|
+
if (await stateManager.containsState(ENVIRONMENT_KEY)) {
|
|
28
|
+
const jsonEnv = await stateManager.load(ENVIRONMENT_KEY);
|
|
29
|
+
const ret = new Environment(); // we do this to ensure that the object has the correct prototype
|
|
30
|
+
if (jsonEnv.servers) {
|
|
31
|
+
ret.servers = jsonEnv.servers;
|
|
32
|
+
}
|
|
33
|
+
ret.defaultServer = jsonEnv.defaultServer;
|
|
34
|
+
return ret;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
return new Environment();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export async function saveEnvironment(dataDir, environment) {
|
|
41
|
+
const stateManager = createStateManager(dataDir);
|
|
42
|
+
await stateManager.save(ENVIRONMENT_KEY, environment);
|
|
43
|
+
}
|
|
44
|
+
export async function resolveServer(dataDir, server) {
|
|
45
|
+
const environment = await loadEnvironment(dataDir);
|
|
46
|
+
let ret;
|
|
47
|
+
if (server) {
|
|
48
|
+
ret = environment.findServer(server);
|
|
49
|
+
if (ret === null) {
|
|
50
|
+
ret = new ServerConfiguration();
|
|
51
|
+
ret.name = server;
|
|
52
|
+
ret.url = server;
|
|
53
|
+
environment.servers.push(ret);
|
|
54
|
+
environment.defaultServer = ret;
|
|
55
|
+
await saveEnvironment(dataDir, environment);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
if (!environment.defaultServer) {
|
|
60
|
+
throw new Error("No Default Server configured. Please specify a Server or set a Default Server.");
|
|
61
|
+
}
|
|
62
|
+
ret = environment.defaultServer;
|
|
63
|
+
}
|
|
64
|
+
return ret;
|
|
65
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface IStateManager {
|
|
2
|
+
/**
|
|
3
|
+
* Checks if the state manager contains a state for the given key
|
|
4
|
+
* @param key the key to check for
|
|
5
|
+
*/
|
|
6
|
+
containsState(key: string): Promise<boolean>;
|
|
7
|
+
/**
|
|
8
|
+
* Saves the given state to the state manager
|
|
9
|
+
* @param key the key to save the state under
|
|
10
|
+
* @param state the state to save
|
|
11
|
+
*/
|
|
12
|
+
save<T>(key: string, state: T): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Loads the state for the given key
|
|
15
|
+
* @param key the key to load the state for
|
|
16
|
+
*/
|
|
17
|
+
load<T>(key: string): Promise<T>;
|
|
18
|
+
}
|
|
19
|
+
export declare function createStateManager(dataDir: string): IStateManager;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fsPromises from 'fs/promises';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
class DefaultStateManager {
|
|
5
|
+
dataDir;
|
|
6
|
+
constructor(dataDir) {
|
|
7
|
+
this.dataDir = dataDir;
|
|
8
|
+
}
|
|
9
|
+
async containsState(key) {
|
|
10
|
+
const filePath = path.resolve(this.dataDir, `${key}.json`);
|
|
11
|
+
if (fs.existsSync(filePath)) {
|
|
12
|
+
return Promise.resolve(true);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
return Promise.resolve(false);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
async load(key) {
|
|
19
|
+
const filePath = path.resolve(this.dataDir, `${key}.json`);
|
|
20
|
+
if (fs.existsSync(filePath)) {
|
|
21
|
+
const data = await fsPromises.readFile(filePath, { encoding: 'utf-8' });
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(data);
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
return Promise.reject('Failed to parse JSON for filePath: ' + filePath + '\n' + e);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
return Promise.reject('State not found for key: ' + key);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async save(key, state) {
|
|
34
|
+
const filePath = path.resolve(this.dataDir, `${key}.json`);
|
|
35
|
+
await fsPromises.mkdir(path.dirname(filePath), { recursive: true });
|
|
36
|
+
return fsPromises.writeFile(filePath, JSON.stringify(state, null, 2));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function createStateManager(dataDir) {
|
|
40
|
+
return new DefaultStateManager(dataDir);
|
|
41
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { KinoticProjectConfig } from '@kinotic-ai/core';
|
|
2
|
+
/**
|
|
3
|
+
* Saves a KinoticProjectConfig to the .config directory using the appropriate Liquid template.
|
|
4
|
+
* @param config The config object to save
|
|
5
|
+
* @param configDir The directory to save the config file in (usually .config)
|
|
6
|
+
*/
|
|
7
|
+
export declare function saveKinoticProjectConfig(config: KinoticProjectConfig, configDir: string): Promise<void>;
|
|
8
|
+
export declare function isKinoticProject(): Promise<boolean>;
|
|
9
|
+
export declare function loadKinoticProjectConfig(): Promise<KinoticProjectConfig>;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { loadConfig } from 'c12';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fsPromises from 'fs/promises';
|
|
4
|
+
import { Liquid } from 'liquidjs';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
/**
|
|
7
|
+
* Returns the absolute path to the first supported kinotic.config.* file in the .config directory, or undefined if none found.
|
|
8
|
+
*/
|
|
9
|
+
async function findKinoticConfigFile() {
|
|
10
|
+
const configDir = path.resolve(process.cwd(), '.config');
|
|
11
|
+
try {
|
|
12
|
+
const stat = await fsPromises.stat(configDir);
|
|
13
|
+
if (stat.isDirectory()) {
|
|
14
|
+
const files = await fsPromises.readdir(configDir);
|
|
15
|
+
const supported = files.filter(f => f.startsWith('kinotic.config.'));
|
|
16
|
+
if (supported.length > 0) {
|
|
17
|
+
return path.join(configDir, supported[0]);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
// Directory does not exist or is not accessible
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Helper function to render a value as TypeScript code with proper indentation
|
|
28
|
+
*/
|
|
29
|
+
function renderValue(value, indent = 0) {
|
|
30
|
+
const indentStr = ' '.repeat(indent);
|
|
31
|
+
const nextIndentStr = ' '.repeat(indent + 1);
|
|
32
|
+
if (value === null) {
|
|
33
|
+
return 'null';
|
|
34
|
+
}
|
|
35
|
+
if (value === undefined) {
|
|
36
|
+
return 'undefined';
|
|
37
|
+
}
|
|
38
|
+
if (typeof value === 'boolean') {
|
|
39
|
+
return String(value);
|
|
40
|
+
}
|
|
41
|
+
if (typeof value === 'string') {
|
|
42
|
+
// JSON.stringify produces a valid JS/TS string literal with correct escaping
|
|
43
|
+
// (handles backslashes, quotes, newlines, tabs, etc.)
|
|
44
|
+
return JSON.stringify(value);
|
|
45
|
+
}
|
|
46
|
+
if (typeof value === 'number') {
|
|
47
|
+
return String(value);
|
|
48
|
+
}
|
|
49
|
+
if (Array.isArray(value)) {
|
|
50
|
+
if (value.length === 0) {
|
|
51
|
+
return '[]';
|
|
52
|
+
}
|
|
53
|
+
const items = value.map(item => `${nextIndentStr}${renderValue(item, indent + 1)}`).join(',\n');
|
|
54
|
+
return `[\n${items}\n${indentStr}]`;
|
|
55
|
+
}
|
|
56
|
+
if (typeof value === 'object') {
|
|
57
|
+
const entries = Object.entries(value)
|
|
58
|
+
.filter(([key, val]) => val !== undefined); // Only filter out undefined values
|
|
59
|
+
if (entries.length === 0) {
|
|
60
|
+
return '{}';
|
|
61
|
+
}
|
|
62
|
+
const props = entries
|
|
63
|
+
.map(([key, val]) => `${nextIndentStr}${key}: ${renderValue(val, indent + 1)}`)
|
|
64
|
+
.join(',\n');
|
|
65
|
+
return `{\n${props}\n${indentStr}}`;
|
|
66
|
+
}
|
|
67
|
+
return 'undefined';
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Saves a KinoticProjectConfig to the .config directory using the appropriate Liquid template.
|
|
71
|
+
* @param config The config object to save
|
|
72
|
+
* @param configDir The directory to save the config file in (usually .config)
|
|
73
|
+
*/
|
|
74
|
+
export async function saveKinoticProjectConfig(config, configDir) {
|
|
75
|
+
const engine = new Liquid({
|
|
76
|
+
root: path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../templates/'),
|
|
77
|
+
extname: '.liquid'
|
|
78
|
+
});
|
|
79
|
+
// Register custom tags for rendering config values
|
|
80
|
+
engine.registerTag('render_value', {
|
|
81
|
+
parse: function (tagToken) {
|
|
82
|
+
const args = tagToken.args.trim().split(/\s+/);
|
|
83
|
+
this.value = args[0];
|
|
84
|
+
this.indent = args[1] || '0'; // Default indent to 0
|
|
85
|
+
},
|
|
86
|
+
render: function* (ctx) {
|
|
87
|
+
const value = yield engine.evalValue(this.value, ctx);
|
|
88
|
+
const indentLevel = yield engine.evalValue(this.indent, ctx);
|
|
89
|
+
return renderValue(value, indentLevel || 0);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
const outFile = path.join(configDir, 'kinotic.config.ts');
|
|
93
|
+
const templateFile = 'KinoticProjectConfig.ts.liquid';
|
|
94
|
+
const renderContext = { config };
|
|
95
|
+
try {
|
|
96
|
+
await fsPromises.mkdir(configDir);
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
// Directory may already exist
|
|
100
|
+
}
|
|
101
|
+
const fileContent = await engine.renderFile(templateFile, renderContext);
|
|
102
|
+
await fsPromises.writeFile(outFile, fileContent);
|
|
103
|
+
}
|
|
104
|
+
export async function isKinoticProject() {
|
|
105
|
+
let result = false;
|
|
106
|
+
if (await findKinoticConfigFile()) {
|
|
107
|
+
result = true;
|
|
108
|
+
}
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
export async function loadKinoticProjectConfig() {
|
|
112
|
+
let result;
|
|
113
|
+
const configFile = await findKinoticConfigFile();
|
|
114
|
+
let configDir = path.resolve(process.cwd(), '.config');
|
|
115
|
+
if (configFile) {
|
|
116
|
+
configDir = path.dirname(configFile);
|
|
117
|
+
const { config } = await loadConfig({
|
|
118
|
+
configFile: configFile,
|
|
119
|
+
name: 'kinotic',
|
|
120
|
+
cwd: configDir,
|
|
121
|
+
dotenv: false,
|
|
122
|
+
packageJson: false
|
|
123
|
+
});
|
|
124
|
+
if (!config) {
|
|
125
|
+
throw new Error(`Failed to load config from ${configFile}`);
|
|
126
|
+
}
|
|
127
|
+
result = config;
|
|
128
|
+
}
|
|
129
|
+
if (!result) {
|
|
130
|
+
throw new Error('No kinotic project config found and not a legacy project');
|
|
131
|
+
}
|
|
132
|
+
// If name is not set, try to load from package.json in cwd
|
|
133
|
+
if (!result.name || !result.description) {
|
|
134
|
+
try {
|
|
135
|
+
const pkgPath = path.resolve(process.cwd(), 'package.json');
|
|
136
|
+
const pkgRaw = await fsPromises.readFile(pkgPath, 'utf-8');
|
|
137
|
+
const pkg = JSON.parse(pkgRaw);
|
|
138
|
+
if (!result.name && pkg.name) {
|
|
139
|
+
result.name = pkg.name;
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
throw new Error('No "name" field found in package.json. Please set the name in your KinoticProjectConfig or package.json.');
|
|
143
|
+
}
|
|
144
|
+
if (!result.description && pkg.description) {
|
|
145
|
+
result.description = pkg.description;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
catch (e) {
|
|
149
|
+
throw new Error('Could not determine project name. Please set the name in your KinoticProjectConfig.\nOriginal error: ' + (e instanceof Error ? e.message : String(e)));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type IAdminEntitiesService } from '@kinotic-ai/persistence'
|
|
2
|
+
import { Base{{ entityName }}AdminEntityService } from './generated/Base{{ entityName }}AdminEntityService{{ fileExtensionForImports }}'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Admin Service for interacting with {{ entityName }} entities
|
|
6
|
+
* This class was generated by the Kinotic CLI
|
|
7
|
+
*/
|
|
8
|
+
export class {{ entityName }}AdminEntityService extends Base{{ entityName }}AdminEntityService {
|
|
9
|
+
|
|
10
|
+
constructor(adminEntitiesService?: IAdminEntitiesService) {
|
|
11
|
+
super(adminEntitiesService)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { AdminEntityService, type IAdminEntitiesService } from '@kinotic-ai/persistence'
|
|
2
|
+
{%- if defaultExport == true %}
|
|
3
|
+
import {{ entityName }} from '{{ entityImportPath }}'
|
|
4
|
+
{%- else %}
|
|
5
|
+
import { {{ entityName }} } from '{{ entityImportPath }}'
|
|
6
|
+
{%- endif %}
|
|
7
|
+
{{ importStatements }}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Admin Base Service for interacting with {{ entityName }} entities
|
|
11
|
+
* This class was generated by the Kinotic CLI. And will be overwritten if the CLI is run again.
|
|
12
|
+
*/
|
|
13
|
+
export class Base{{ entityName }}AdminEntityService extends AdminEntityService<{{ entityName }}> {
|
|
14
|
+
|
|
15
|
+
constructor(adminEntitiesService?: IAdminEntitiesService) {
|
|
16
|
+
super('{{ entityNamespace }}', '{{ entityName }}', adminEntitiesService)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { EntityService, type IEntitiesService } from '@kinotic-ai/persistence'
|
|
2
|
+
{%- if defaultExport == true %}
|
|
3
|
+
import {{ entityName }} from '{{ entityImportPath }}'
|
|
4
|
+
{%- else %}
|
|
5
|
+
import { {{ entityName }} } from '{{ entityImportPath }}'
|
|
6
|
+
{%- endif %}
|
|
7
|
+
{{ importStatements }}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Base Service for interacting with {{ entityName }} entities
|
|
11
|
+
* This class was generated by the Kinotic CLI. And will be overwritten if the CLI is run again.
|
|
12
|
+
*/
|
|
13
|
+
export class Base{{ entityName }}EntityService extends EntityService<{{ entityName }}> {
|
|
14
|
+
|
|
15
|
+
private readonly shouldValidate: boolean
|
|
16
|
+
|
|
17
|
+
constructor(shouldValidate: boolean = true, entitiesService?: IEntitiesService) {
|
|
18
|
+
super('{{ entityNamespace }}', '{{ entityName }}', entitiesService)
|
|
19
|
+
this.shouldValidate = shouldValidate
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
protected async beforeSaveOrUpdate(entity: {{ entityName }}): Promise<{{ entityName }}> {
|
|
23
|
+
if (this.shouldValidate) {
|
|
24
|
+
return this.validate(entity)
|
|
25
|
+
} else {
|
|
26
|
+
return entity
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
protected async beforeBulkSaveOrUpdate(entities: {{ entityName }}[]): Promise<{{ entityName }}[]> {
|
|
31
|
+
if (this.shouldValidate) {
|
|
32
|
+
const validatedEntities: {{ entityName }}[] = []
|
|
33
|
+
for (let entity of entities) {
|
|
34
|
+
validatedEntities.push(this.validate(entity))
|
|
35
|
+
}
|
|
36
|
+
return validatedEntities
|
|
37
|
+
} else {
|
|
38
|
+
return entities
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
validate(entity: {{ entityName }}): {{ entityName }} {
|
|
43
|
+
let ret: any
|
|
44
|
+
{{ validationLogic }}
|
|
45
|
+
return ret
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
}
|