@kinotic-ai/kinotic-cli 2.2.0 → 2.3.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 +1 -393
- package/dist/commands/login.d.ts +9 -0
- package/dist/commands/login.js +21 -0
- package/dist/commands/synchronize.d.ts +0 -1
- package/dist/commands/synchronize.js +38 -37
- package/dist/internal/CliAuthenticator.d.ts +46 -0
- package/dist/internal/CliAuthenticator.js +217 -0
- package/dist/internal/Utils.d.ts +0 -16
- package/dist/internal/Utils.js +0 -141
- package/dist/templates/entity/BaseRepository.liquid +2 -2
- package/package.json +9 -29
- package/bin/dev.cmd +0 -3
- package/bin/dev.js +0 -6
- package/bin/run.cmd +0 -3
- package/bin/run.js +0 -6
- package/oclif.manifest.json +0 -213
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { ConnectionInfo, Kinotic } from '@kinotic-ai/core';
|
|
2
|
+
import { confirm } from '@inquirer/prompts';
|
|
3
|
+
import open from 'open';
|
|
4
|
+
import pTimeout from 'p-timeout';
|
|
5
|
+
import { WebSocket } from 'ws';
|
|
6
|
+
import { createStateManager } from './state/IStateManager.js';
|
|
7
|
+
/** State key the rotating refresh token is persisted under, keyed by server url. */
|
|
8
|
+
const CREDENTIALS_KEY = 'kinotic-credentials';
|
|
9
|
+
/** Per-request timeout for REST calls to the Kinotic Server. */
|
|
10
|
+
const FETCH_TIMEOUT_MS = 30_000;
|
|
11
|
+
/**
|
|
12
|
+
* CLI authentication against a Kinotic server using the OAuth 2.0 Device Authorization Grant
|
|
13
|
+
* (RFC 8628). {@link login} runs the interactive browser flow once and stores the refresh
|
|
14
|
+
* token; {@link connect} uses that stored token to open an authenticated {@link Kinotic}
|
|
15
|
+
* connection, refreshing the short-lived access token before every (re)connect.
|
|
16
|
+
*/
|
|
17
|
+
export class CliAuthenticator {
|
|
18
|
+
server;
|
|
19
|
+
configDir;
|
|
20
|
+
logger;
|
|
21
|
+
refreshToken = null;
|
|
22
|
+
accessToken = null;
|
|
23
|
+
accessTokenExpiresAt = 0;
|
|
24
|
+
/**
|
|
25
|
+
* @param server the server url to authenticate against
|
|
26
|
+
* @param configDir directory the rotating refresh token is persisted in
|
|
27
|
+
* @param logger sink for user-facing progress messages
|
|
28
|
+
*/
|
|
29
|
+
constructor(server, configDir, logger) {
|
|
30
|
+
this.server = server;
|
|
31
|
+
this.configDir = configDir;
|
|
32
|
+
this.logger = logger;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Runs the interactive device-authorization login and persists the refresh token, so
|
|
36
|
+
* later {@link connect} calls — this run or a future one — are non-interactive.
|
|
37
|
+
*
|
|
38
|
+
* @return true if login succeeded
|
|
39
|
+
*/
|
|
40
|
+
async login() {
|
|
41
|
+
const target = this.parseServer();
|
|
42
|
+
if (target === null) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
const tokens = await this.deviceLogin(target.restBaseUrl);
|
|
46
|
+
if (tokens === null) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
await this.saveRefreshToken(tokens.refresh_token);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Opens an authenticated {@link Kinotic} connection using the stored refresh token. Fails
|
|
54
|
+
* fast — with no interactive prompt — when there are no stored credentials.
|
|
55
|
+
*
|
|
56
|
+
* @return true if the connection was established
|
|
57
|
+
*/
|
|
58
|
+
async connect() {
|
|
59
|
+
try {
|
|
60
|
+
const target = this.parseServer();
|
|
61
|
+
if (target === null) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
this.refreshToken = await this.loadRefreshToken();
|
|
65
|
+
if (this.refreshToken === null) {
|
|
66
|
+
this.logger.log('Not logged in. Run `kinotic login` first.');
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
const connectionInfo = new ConnectionInfo();
|
|
70
|
+
connectionInfo.host = target.host;
|
|
71
|
+
connectionInfo.port = target.port;
|
|
72
|
+
connectionInfo.useSSL = target.useSSL;
|
|
73
|
+
// The CLI is a Node client: it attaches the access token as a WebSocket upgrade
|
|
74
|
+
// header. The factory is async so the token is refreshed before each (re)connect.
|
|
75
|
+
connectionInfo.webSocketFactory = async () => {
|
|
76
|
+
const token = await this.freshAccessToken(target.restBaseUrl);
|
|
77
|
+
return new WebSocket(target.wsUrl, {
|
|
78
|
+
headers: { Authorization: 'Bearer ' + token }
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
await pTimeout(Kinotic.connect(connectionInfo), {
|
|
82
|
+
milliseconds: 60000,
|
|
83
|
+
message: 'Connection timeout trying to connect to the Kinotic Server'
|
|
84
|
+
});
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
this.logger.log('Could not connect to the Kinotic Server: '
|
|
89
|
+
+ (e instanceof Error ? e.message : String(e)));
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Returns a valid access token, refreshing it — and persisting the rotated refresh token —
|
|
95
|
+
* when it is absent or within 10s of expiry.
|
|
96
|
+
*/
|
|
97
|
+
async freshAccessToken(restBaseUrl) {
|
|
98
|
+
if (this.accessToken !== null && Date.now() < this.accessTokenExpiresAt - 10_000) {
|
|
99
|
+
return this.accessToken;
|
|
100
|
+
}
|
|
101
|
+
const res = await fetch(restBaseUrl + '/api/login/device/refresh', {
|
|
102
|
+
method: 'POST',
|
|
103
|
+
headers: { 'Content-Type': 'application/json' },
|
|
104
|
+
body: JSON.stringify({ refresh_token: this.refreshToken }),
|
|
105
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
106
|
+
});
|
|
107
|
+
if (!res.ok) {
|
|
108
|
+
throw new Error('Session expired. Run `kinotic login` again.');
|
|
109
|
+
}
|
|
110
|
+
const tokens = await res.json();
|
|
111
|
+
this.refreshToken = tokens.refresh_token;
|
|
112
|
+
this.accessToken = tokens.access_token;
|
|
113
|
+
this.accessTokenExpiresAt = Date.now() + (tokens.expires_in ?? 60) * 1000;
|
|
114
|
+
await this.saveRefreshToken(this.refreshToken);
|
|
115
|
+
return this.accessToken;
|
|
116
|
+
}
|
|
117
|
+
/** Parses the server url into the host/port the gateway serves both REST and STOMP on. */
|
|
118
|
+
parseServer() {
|
|
119
|
+
const url = new URL(this.server);
|
|
120
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
121
|
+
this.logger.log('Invalid server URL, only http and https are supported');
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
const useSSL = url.protocol === 'https:';
|
|
125
|
+
// Locally the server url often points at the static web port; the gateway
|
|
126
|
+
// (REST + STOMP) always listens on 58503, so the port is overridden.
|
|
127
|
+
let port;
|
|
128
|
+
if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {
|
|
129
|
+
port = 58503;
|
|
130
|
+
}
|
|
131
|
+
else if (url.port) {
|
|
132
|
+
port = Number(url.port);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
port = useSSL ? 443 : 58503;
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
host: url.hostname,
|
|
139
|
+
port,
|
|
140
|
+
useSSL,
|
|
141
|
+
restBaseUrl: (useSSL ? 'https' : 'http') + '://' + url.hostname + ':' + port,
|
|
142
|
+
wsUrl: (useSSL ? 'wss' : 'ws') + '://' + url.hostname + ':' + port + '/v1'
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/** Runs the RFC 8628 device flow: start, browser approval, then poll for tokens. */
|
|
146
|
+
async deviceLogin(restBaseUrl) {
|
|
147
|
+
const startRes = await fetch(restBaseUrl + '/api/login/device/start', {
|
|
148
|
+
method: 'POST',
|
|
149
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
150
|
+
});
|
|
151
|
+
if (!startRes.ok) {
|
|
152
|
+
this.logger.log('Could not start device authorization with the Kinotic Server.');
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
const start = await startRes.json();
|
|
156
|
+
this.logger.log('Authenticate your account at:');
|
|
157
|
+
this.logger.log(start.verification_uri_complete);
|
|
158
|
+
this.logger.log(`Your code is: ${start.user_code}`);
|
|
159
|
+
const answer = await confirm({ message: 'Open in browser?', default: true });
|
|
160
|
+
if (answer) {
|
|
161
|
+
await open(start.verification_uri_complete);
|
|
162
|
+
}
|
|
163
|
+
const deadline = Date.now() + start.expires_in * 1000;
|
|
164
|
+
let intervalMs = Math.max(start.interval, 1) * 1000;
|
|
165
|
+
while (Date.now() < deadline) {
|
|
166
|
+
await delay(intervalMs);
|
|
167
|
+
const tokenRes = await fetch(restBaseUrl + '/api/login/device/token', {
|
|
168
|
+
method: 'POST',
|
|
169
|
+
headers: { 'Content-Type': 'application/json' },
|
|
170
|
+
body: JSON.stringify({ device_code: start.device_code }),
|
|
171
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
172
|
+
});
|
|
173
|
+
if (tokenRes.ok) {
|
|
174
|
+
return await tokenRes.json();
|
|
175
|
+
}
|
|
176
|
+
const error = await readErrorCode(tokenRes);
|
|
177
|
+
if (error === 'slow_down') {
|
|
178
|
+
intervalMs += 5000;
|
|
179
|
+
}
|
|
180
|
+
else if (error !== 'authorization_pending') {
|
|
181
|
+
this.logger.log(`Device authorization failed: ${error}`);
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
this.logger.log('Device authorization timed out before it was approved.');
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
async loadRefreshToken() {
|
|
189
|
+
const stateManager = createStateManager(this.configDir);
|
|
190
|
+
if (!(await stateManager.containsState(CREDENTIALS_KEY))) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
const credentials = await stateManager.load(CREDENTIALS_KEY);
|
|
194
|
+
return credentials[this.server] ?? null;
|
|
195
|
+
}
|
|
196
|
+
async saveRefreshToken(refreshToken) {
|
|
197
|
+
const stateManager = createStateManager(this.configDir);
|
|
198
|
+
let credentials = {};
|
|
199
|
+
if (await stateManager.containsState(CREDENTIALS_KEY)) {
|
|
200
|
+
credentials = await stateManager.load(CREDENTIALS_KEY);
|
|
201
|
+
}
|
|
202
|
+
credentials[this.server] = refreshToken;
|
|
203
|
+
await stateManager.save(CREDENTIALS_KEY, credentials);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
async function readErrorCode(res) {
|
|
207
|
+
try {
|
|
208
|
+
const body = await res.json();
|
|
209
|
+
return body.error ?? 'unknown_error';
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return 'unknown_error';
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function delay(ms) {
|
|
216
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
217
|
+
}
|
package/dist/internal/Utils.d.ts
CHANGED
|
@@ -5,23 +5,7 @@ export type GeneratedServiceInfo = {
|
|
|
5
5
|
entityServiceName: string;
|
|
6
6
|
namedQueries: FunctionDefinition[];
|
|
7
7
|
};
|
|
8
|
-
export declare class SessionMetadata {
|
|
9
|
-
sessionId: string;
|
|
10
|
-
replyToId: string;
|
|
11
|
-
constructor(sessionId: string, replyToId: string);
|
|
12
|
-
}
|
|
13
8
|
export declare function jsonStringifyReplacer(key: any, value: any): any;
|
|
14
|
-
/**
|
|
15
|
-
* Connects to the server and upgrades the session to a CLI session
|
|
16
|
-
* Currently this works by connecting and waiting for the clients session id on the event bus
|
|
17
|
-
* The cli then disconnects and reconnects using the clients' session.
|
|
18
|
-
* This will be replaced when the server supports a session upgrade command
|
|
19
|
-
* @param server the server to connect to
|
|
20
|
-
* @param logger the logger to use
|
|
21
|
-
* @param authHeadersFile path to a file containing the auth headers
|
|
22
|
-
* @return true if the session was upgraded successfully
|
|
23
|
-
*/
|
|
24
|
-
export declare function connectAndUpgradeSession(server: string, logger: Logger, authHeadersFile?: string): Promise<boolean>;
|
|
25
9
|
export type EntityInfo = {
|
|
26
10
|
exportedFromFile: string;
|
|
27
11
|
defaultExport: boolean;
|
package/dist/internal/Utils.js
CHANGED
|
@@ -1,24 +1,11 @@
|
|
|
1
|
-
import { Kinotic, Event, EventConstants, ParticipantConstants } from '@kinotic-ai/core';
|
|
2
1
|
import { ObjectC3Type } from '@kinotic-ai/idl';
|
|
3
2
|
import fs from 'fs';
|
|
4
3
|
import fsPromises from 'fs/promises';
|
|
5
|
-
import { confirm } from '@inquirer/prompts';
|
|
6
|
-
import open from 'open';
|
|
7
|
-
import pTimeout from 'p-timeout';
|
|
8
4
|
import path from 'path';
|
|
9
5
|
import { IndentationText, Node, Project } from 'ts-morph';
|
|
10
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
11
6
|
import { createConversionContext } from './converter/IConversionContext.js';
|
|
12
7
|
import { TypescriptConversionState } from './converter/typescript/TypescriptConversionState.js';
|
|
13
8
|
import { TypescriptConverterStrategy } from './converter/typescript/TypescriptConverterStrategy.js';
|
|
14
|
-
export class SessionMetadata {
|
|
15
|
-
sessionId;
|
|
16
|
-
replyToId;
|
|
17
|
-
constructor(sessionId, replyToId) {
|
|
18
|
-
this.sessionId = sessionId;
|
|
19
|
-
this.replyToId = replyToId;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
9
|
function isEmpty(value) {
|
|
23
10
|
if (value === null || value === undefined) {
|
|
24
11
|
return true;
|
|
@@ -36,134 +23,6 @@ export function jsonStringifyReplacer(key, value) {
|
|
|
36
23
|
? undefined
|
|
37
24
|
: value;
|
|
38
25
|
}
|
|
39
|
-
/**
|
|
40
|
-
* Connects to the server and upgrades the session to a CLI session
|
|
41
|
-
* Currently this works by connecting and waiting for the clients session id on the event bus
|
|
42
|
-
* The cli then disconnects and reconnects using the clients' session.
|
|
43
|
-
* This will be replaced when the server supports a session upgrade command
|
|
44
|
-
* @param server the server to connect to
|
|
45
|
-
* @param logger the logger to use
|
|
46
|
-
* @param authHeadersFile path to a file containing the auth headers
|
|
47
|
-
* @return true if the session was upgraded successfully
|
|
48
|
-
*/
|
|
49
|
-
export async function connectAndUpgradeSession(server, logger, authHeadersFile) {
|
|
50
|
-
try {
|
|
51
|
-
const serverURL = new URL(server);
|
|
52
|
-
if (serverURL.protocol !== 'http:' && serverURL.protocol !== 'https:') {
|
|
53
|
-
logger.log('Invalid server URL, only http and https are supported');
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
let connectionInfo = { host: '' };
|
|
57
|
-
if (serverURL.hostname === 'localhost' || serverURL.hostname === '127.0.0.1') {
|
|
58
|
-
connectionInfo.host = serverURL.hostname;
|
|
59
|
-
connectionInfo.port = 58503;
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
connectionInfo.host = serverURL.hostname;
|
|
63
|
-
if (serverURL.protocol === 'https:') {
|
|
64
|
-
connectionInfo.useSSL = true;
|
|
65
|
-
connectionInfo.port = 443;
|
|
66
|
-
}
|
|
67
|
-
if (serverURL.port) {
|
|
68
|
-
connectionInfo.port = Number(serverURL.port);
|
|
69
|
-
}
|
|
70
|
-
else if (!connectionInfo.useSSL) {
|
|
71
|
-
connectionInfo.port = 58503;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
if (authHeadersFile) {
|
|
75
|
-
if (!fs.existsSync(authHeadersFile)) {
|
|
76
|
-
logger.log(`Authentication header file ${authHeadersFile} does not exist. Please provide a valid file.`);
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
logger.log(`Authentication header file ${authHeadersFile} loaded successfully. Using authentication headers to connect to the Kinotic Server.`);
|
|
81
|
-
}
|
|
82
|
-
const authHeaders = JSON.parse(fs.readFileSync(authHeadersFile, 'utf8'));
|
|
83
|
-
connectionInfo.connectHeaders = authHeaders;
|
|
84
|
-
const connectedInfo = await pTimeout(Kinotic.connect(connectionInfo), {
|
|
85
|
-
milliseconds: 60000,
|
|
86
|
-
message: 'Connection timeout trying to connect to the Kinotic Server'
|
|
87
|
-
});
|
|
88
|
-
if (connectedInfo) {
|
|
89
|
-
return true;
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
connectionInfo.connectHeaders = {
|
|
97
|
-
login: ParticipantConstants.CLI_PARTICIPANT_ID
|
|
98
|
-
};
|
|
99
|
-
const connectedInfo = await pTimeout(Kinotic.connect(connectionInfo), {
|
|
100
|
-
milliseconds: 60000,
|
|
101
|
-
message: 'Connection timeout trying to connect to the Kinotic Server'
|
|
102
|
-
});
|
|
103
|
-
if (connectedInfo) {
|
|
104
|
-
// This works because any client can subscribe to a destination that is scoped to the connectedInfo.replyToId
|
|
105
|
-
const scope = connectedInfo.replyToId + ':' + uuidv4();
|
|
106
|
-
const url = server + (server.endsWith('/') ? '' : '/') + '#/sessionUpgrade/' + encodeURIComponent(scope);
|
|
107
|
-
logger.log('Authenticate your account at:');
|
|
108
|
-
logger.log(url);
|
|
109
|
-
const answer = await confirm({
|
|
110
|
-
message: 'Open in browser?',
|
|
111
|
-
default: true,
|
|
112
|
-
});
|
|
113
|
-
if (answer) {
|
|
114
|
-
await open(url);
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
logger.log('Browser will not be opened. You must authenticate your account before continuing.');
|
|
118
|
-
}
|
|
119
|
-
const sessionMetadata = await receiveSessionId(scope);
|
|
120
|
-
await Kinotic.disconnect();
|
|
121
|
-
connectionInfo.connectHeaders = {
|
|
122
|
-
session: sessionMetadata.sessionId
|
|
123
|
-
};
|
|
124
|
-
// Provide this so the continuum client will use the same replyToId as the session
|
|
125
|
-
connectionInfo.connectHeaders[EventConstants.REPLY_TO_ID_HEADER] = sessionMetadata.replyToId;
|
|
126
|
-
await Kinotic.connect(connectionInfo);
|
|
127
|
-
logger.log('Authenticated successfully\n');
|
|
128
|
-
return true;
|
|
129
|
-
}
|
|
130
|
-
else {
|
|
131
|
-
logger.log("Could not connect to the Kinotic Server. Please check the server is running and the URL is correct.");
|
|
132
|
-
return false;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
catch (e) {
|
|
137
|
-
logger.log("Could not connect to the Kinotic Server. Please check the server is running and the URL is correct.", e);
|
|
138
|
-
return false;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
function receiveSessionId(scope) {
|
|
142
|
-
const subscribeCRI = EventConstants.SERVICE_DESTINATION_PREFIX + scope + '@continuum.cli.SessionUpgradeService';
|
|
143
|
-
return new Promise((resolve, reject) => {
|
|
144
|
-
const subscription = Kinotic.eventBus.observe(subscribeCRI).subscribe((value) => {
|
|
145
|
-
// send reply to user
|
|
146
|
-
const replyTo = value.getHeader(EventConstants.REPLY_TO_HEADER);
|
|
147
|
-
if (replyTo) {
|
|
148
|
-
const replyEvent = new Event(replyTo);
|
|
149
|
-
const correlationId = value.getHeader(EventConstants.CORRELATION_ID_HEADER);
|
|
150
|
-
if (correlationId) {
|
|
151
|
-
replyEvent.setHeader(EventConstants.CORRELATION_ID_HEADER, correlationId);
|
|
152
|
-
}
|
|
153
|
-
replyEvent.setHeader(EventConstants.CONTENT_TYPE_HEADER, EventConstants.CONTENT_JSON);
|
|
154
|
-
Kinotic.eventBus.send(replyEvent);
|
|
155
|
-
}
|
|
156
|
-
subscription.unsubscribe();
|
|
157
|
-
const jsonObj = JSON.parse(value.getDataString());
|
|
158
|
-
if (jsonObj?.length > 0) {
|
|
159
|
-
resolve(jsonObj[0]);
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
reject('No Session Id found in data');
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
26
|
function getEntityDecoratorIfExists(node) {
|
|
168
27
|
if (Node.isClassDeclaration(node)) {
|
|
169
28
|
return node.getDecorator('Entity');
|
|
@@ -19,7 +19,7 @@ export class Base{{ entityName }}Repository extends EntityRepository<{{ entityNa
|
|
|
19
19
|
this.shouldValidate = shouldValidate
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
protected async beforeSaveOrUpdate(entity: {{ entityName }}): Promise<{{ entityName }}> {
|
|
22
|
+
protected override async beforeSaveOrUpdate(entity: {{ entityName }}): Promise<{{ entityName }}> {
|
|
23
23
|
if (this.shouldValidate) {
|
|
24
24
|
return this.validate(entity)
|
|
25
25
|
} else {
|
|
@@ -27,7 +27,7 @@ export class Base{{ entityName }}Repository extends EntityRepository<{{ entityNa
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
protected async beforeBulkSaveOrUpdate(entities: {{ entityName }}[]): Promise<{{ entityName }}[]> {
|
|
30
|
+
protected override async beforeBulkSaveOrUpdate(entities: {{ entityName }}[]): Promise<{{ entityName }}[]> {
|
|
31
31
|
if (this.shouldValidate) {
|
|
32
32
|
const validatedEntities: {{ entityName }}[] = []
|
|
33
33
|
for (let entity of entities) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kinotic-ai/kinotic-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Kinotic CLI provides the ability to build, deploy, and manage Kinotic applications that run on the Kinotic OS.",
|
|
5
5
|
"author": "Kinotic Developers",
|
|
6
6
|
"bin": {
|
|
@@ -14,34 +14,27 @@
|
|
|
14
14
|
"files": [
|
|
15
15
|
"/bin",
|
|
16
16
|
"/dist",
|
|
17
|
-
"/npm-shrinkwrap.json"
|
|
18
|
-
"/oclif.manifest.json"
|
|
17
|
+
"/npm-shrinkwrap.json"
|
|
19
18
|
],
|
|
20
19
|
"dependencies": {
|
|
21
20
|
"@inquirer/prompts": "^8.3.2",
|
|
22
|
-
"@kinotic-ai/core": "^1.
|
|
21
|
+
"@kinotic-ai/core": "^1.4.0",
|
|
23
22
|
"@kinotic-ai/idl": "^1.0.9",
|
|
24
|
-
"@kinotic-ai/os-api": "^1.1
|
|
25
|
-
"@kinotic-ai/persistence": "^1.2.
|
|
23
|
+
"@kinotic-ai/os-api": "^1.5.1",
|
|
24
|
+
"@kinotic-ai/persistence": "^1.2.1",
|
|
26
25
|
"@oclif/core": "^4.9.0",
|
|
27
|
-
"@oclif/plugin-autocomplete": "^3.2.41",
|
|
28
|
-
"@oclif/plugin-help": "^6",
|
|
29
|
-
"@oclif/plugin-not-found": "^3.2.75",
|
|
30
|
-
"@oclif/plugin-plugins": "^5.4.58",
|
|
31
|
-
"@oclif/plugin-update": "^4.7.23",
|
|
32
|
-
"@oclif/plugin-warn-if-update-available": "^3.1.55",
|
|
33
26
|
"c12": "^3.3.3",
|
|
34
27
|
"chalk": "^5.6.2",
|
|
35
28
|
"execa": "^9.6.1",
|
|
36
29
|
"glob": "^13.0.6",
|
|
37
30
|
"graphql": "^16.13.1",
|
|
38
|
-
"liquidjs": "10.25.
|
|
31
|
+
"liquidjs": "10.25.7",
|
|
39
32
|
"open": "^11.0.0",
|
|
40
33
|
"ora": "^9.3.0",
|
|
41
34
|
"p-timeout": "^7.0.1",
|
|
42
35
|
"radash": "^12.1.1",
|
|
43
36
|
"reflect-metadata": "^0.2.2",
|
|
44
|
-
"simple-git": "
|
|
37
|
+
"simple-git": "3.36.0",
|
|
45
38
|
"terminal-link": "^5.0.0",
|
|
46
39
|
"ts-morph": "^27.0.2",
|
|
47
40
|
"uuid": "^13.0.0",
|
|
@@ -50,7 +43,6 @@
|
|
|
50
43
|
"zod": "^4.3.6"
|
|
51
44
|
},
|
|
52
45
|
"devDependencies": {
|
|
53
|
-
"@oclif/test": "^4.1.16",
|
|
54
46
|
"@types/chai": "^5",
|
|
55
47
|
"@types/inquirer": "^9.0.9",
|
|
56
48
|
"@types/mocha": "^10.0.10",
|
|
@@ -59,10 +51,8 @@
|
|
|
59
51
|
"@types/ws": "^8.18.1",
|
|
60
52
|
"chai": "^6",
|
|
61
53
|
"eslint": "^9",
|
|
62
|
-
"eslint-config-oclif": "^6",
|
|
63
54
|
"eslint-config-prettier": "^10",
|
|
64
55
|
"mocha": "^11",
|
|
65
|
-
"oclif": "^4.22.92",
|
|
66
56
|
"shx": "^0.4.0",
|
|
67
57
|
"tslib": "^2.8.1",
|
|
68
58
|
"tsx": "^4.21.0",
|
|
@@ -73,14 +63,6 @@
|
|
|
73
63
|
"bin": "kinotic",
|
|
74
64
|
"dirname": "kinotic",
|
|
75
65
|
"commands": "./dist/commands",
|
|
76
|
-
"plugins": [
|
|
77
|
-
"@oclif/plugin-help",
|
|
78
|
-
"@oclif/plugin-plugins",
|
|
79
|
-
"@oclif/plugin-update",
|
|
80
|
-
"@oclif/plugin-not-found",
|
|
81
|
-
"@oclif/plugin-warn-if-update-available",
|
|
82
|
-
"@oclif/plugin-autocomplete"
|
|
83
|
-
],
|
|
84
66
|
"topicSeparator": " ",
|
|
85
67
|
"topics": {
|
|
86
68
|
"create": {
|
|
@@ -91,16 +73,14 @@
|
|
|
91
73
|
"engines": {
|
|
92
74
|
"node": ">=18.0.0"
|
|
93
75
|
},
|
|
94
|
-
"keywords": [
|
|
95
|
-
"oclif"
|
|
96
|
-
],
|
|
76
|
+
"keywords": [],
|
|
97
77
|
"types": "dist/index.d.ts",
|
|
98
78
|
"scripts": {
|
|
99
79
|
"build": "shx rm -rf dist && tsc -b && tsc-alias -f && pnpm run copy-files",
|
|
100
80
|
"lint": "eslint . --ext .ts --config .eslintrc",
|
|
101
81
|
"posttest": "pnpm lint",
|
|
102
82
|
"test": "mocha --forbid-only \"test/**/*.test.ts\"",
|
|
103
|
-
"version": "
|
|
83
|
+
"version": "git add README.md",
|
|
104
84
|
"copy-files": "cp -r ./src/templates/ ./dist/templates/"
|
|
105
85
|
}
|
|
106
86
|
}
|
package/bin/dev.cmd
DELETED
package/bin/dev.js
DELETED
package/bin/run.cmd
DELETED