@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.
Files changed (82) hide show
  1. package/README.md +594 -0
  2. package/bin/dev.cmd +3 -0
  3. package/bin/dev.js +6 -0
  4. package/bin/run.cmd +3 -0
  5. package/bin/run.js +6 -0
  6. package/dist/commands/generate.d.ts +11 -0
  7. package/dist/commands/generate.js +36 -0
  8. package/dist/commands/initialize.d.ts +12 -0
  9. package/dist/commands/initialize.js +102 -0
  10. package/dist/commands/synchronize.d.ts +17 -0
  11. package/dist/commands/synchronize.js +154 -0
  12. package/dist/index.d.ts +1 -0
  13. package/dist/index.js +1 -0
  14. package/dist/internal/CodeGenerationService.d.ts +24 -0
  15. package/dist/internal/CodeGenerationService.js +256 -0
  16. package/dist/internal/Logger.d.ts +27 -0
  17. package/dist/internal/Logger.js +35 -0
  18. package/dist/internal/ProjectMigrationService.d.ts +28 -0
  19. package/dist/internal/ProjectMigrationService.js +99 -0
  20. package/dist/internal/Utils.d.ts +66 -0
  21. package/dist/internal/Utils.js +349 -0
  22. package/dist/internal/converter/ConverterConstants.d.ts +3 -0
  23. package/dist/internal/converter/ConverterConstants.js +4 -0
  24. package/dist/internal/converter/DefaultConversionContext.d.ts +29 -0
  25. package/dist/internal/converter/DefaultConversionContext.js +112 -0
  26. package/dist/internal/converter/IConversionContext.d.ts +64 -0
  27. package/dist/internal/converter/IConversionContext.js +11 -0
  28. package/dist/internal/converter/IConverterStrategy.d.ts +35 -0
  29. package/dist/internal/converter/IConverterStrategy.js +1 -0
  30. package/dist/internal/converter/ITypeConverter.d.ts +28 -0
  31. package/dist/internal/converter/ITypeConverter.js +1 -0
  32. package/dist/internal/converter/SpecificTypesConverter.d.ts +12 -0
  33. package/dist/internal/converter/SpecificTypesConverter.js +24 -0
  34. package/dist/internal/converter/codegen/ArrayC3TypeToStatementMapper.d.ts +9 -0
  35. package/dist/internal/converter/codegen/ArrayC3TypeToStatementMapper.js +46 -0
  36. package/dist/internal/converter/codegen/ObjectC3TypeToStatementMapper.d.ts +13 -0
  37. package/dist/internal/converter/codegen/ObjectC3TypeToStatementMapper.js +67 -0
  38. package/dist/internal/converter/codegen/PrimitiveC3TypeToStatementMapper.d.ts +9 -0
  39. package/dist/internal/converter/codegen/PrimitiveC3TypeToStatementMapper.js +24 -0
  40. package/dist/internal/converter/codegen/StatementMapper.d.ts +40 -0
  41. package/dist/internal/converter/codegen/StatementMapper.js +132 -0
  42. package/dist/internal/converter/codegen/StatementMapperConversionState.d.ts +9 -0
  43. package/dist/internal/converter/codegen/StatementMapperConversionState.js +17 -0
  44. package/dist/internal/converter/codegen/StatementMapperConverterStrategy.d.ts +16 -0
  45. package/dist/internal/converter/codegen/StatementMapperConverterStrategy.js +30 -0
  46. package/dist/internal/converter/codegen/UnionC3TypeToStatementMapper.d.ts +9 -0
  47. package/dist/internal/converter/codegen/UnionC3TypeToStatementMapper.js +46 -0
  48. package/dist/internal/converter/common/BaseConversionState.d.ts +9 -0
  49. package/dist/internal/converter/common/BaseConversionState.js +13 -0
  50. package/dist/internal/converter/typescript/ArrayToC3Type.d.ts +9 -0
  51. package/dist/internal/converter/typescript/ArrayToC3Type.js +17 -0
  52. package/dist/internal/converter/typescript/ConverterUtils.d.ts +4 -0
  53. package/dist/internal/converter/typescript/ConverterUtils.js +261 -0
  54. package/dist/internal/converter/typescript/EnumToC3Type.d.ts +12 -0
  55. package/dist/internal/converter/typescript/EnumToC3Type.js +26 -0
  56. package/dist/internal/converter/typescript/ObjectLikeToC3Type.d.ts +15 -0
  57. package/dist/internal/converter/typescript/ObjectLikeToC3Type.js +111 -0
  58. package/dist/internal/converter/typescript/PrimitiveToC3Type.d.ts +10 -0
  59. package/dist/internal/converter/typescript/PrimitiveToC3Type.js +33 -0
  60. package/dist/internal/converter/typescript/QueryOptionsToC3Type.d.ts +9 -0
  61. package/dist/internal/converter/typescript/QueryOptionsToC3Type.js +9 -0
  62. package/dist/internal/converter/typescript/TenantSelectionToC3Type.d.ts +9 -0
  63. package/dist/internal/converter/typescript/TenantSelectionToC3Type.js +9 -0
  64. package/dist/internal/converter/typescript/TypescriptConversionState.d.ts +24 -0
  65. package/dist/internal/converter/typescript/TypescriptConversionState.js +26 -0
  66. package/dist/internal/converter/typescript/TypescriptConverterStrategy.d.ts +16 -0
  67. package/dist/internal/converter/typescript/TypescriptConverterStrategy.js +44 -0
  68. package/dist/internal/converter/typescript/UnionToC3Type.d.ts +15 -0
  69. package/dist/internal/converter/typescript/UnionToC3Type.js +184 -0
  70. package/dist/internal/state/Environment.d.ts +13 -0
  71. package/dist/internal/state/Environment.js +65 -0
  72. package/dist/internal/state/IStateManager.d.ts +19 -0
  73. package/dist/internal/state/IStateManager.js +41 -0
  74. package/dist/internal/state/KinoticProjectConfigUtil.d.ts +9 -0
  75. package/dist/internal/state/KinoticProjectConfigUtil.js +153 -0
  76. package/dist/templates/AdminEntityService.liquid +14 -0
  77. package/dist/templates/BaseAdminEntityService.liquid +19 -0
  78. package/dist/templates/BaseEntityService.liquid +48 -0
  79. package/dist/templates/EntityService.liquid +14 -0
  80. package/dist/templates/KinoticProjectConfig.ts.liquid +10 -0
  81. package/oclif.manifest.json +161 -0
  82. 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
+ }