@revoengine/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 +211 -0
- package/dist/bin/revo.d.ts +2 -0
- package/dist/bin/revo.js +19 -0
- package/dist/src/cli.d.ts +3 -0
- package/dist/src/cli.js +213 -0
- package/dist/src/client.d.ts +72 -0
- package/dist/src/client.js +315 -0
- package/dist/src/commands/auth.d.ts +2 -0
- package/dist/src/commands/auth.js +131 -0
- package/dist/src/commands/component.d.ts +2 -0
- package/dist/src/commands/component.js +905 -0
- package/dist/src/commands/endpoints.d.ts +2 -0
- package/dist/src/commands/endpoints.js +4 -0
- package/dist/src/commands/index.d.ts +7 -0
- package/dist/src/commands/index.js +7 -0
- package/dist/src/commands/info.d.ts +2 -0
- package/dist/src/commands/info.js +6 -0
- package/dist/src/commands/project.d.ts +2 -0
- package/dist/src/commands/project.js +80 -0
- package/dist/src/commands/request.d.ts +2 -0
- package/dist/src/commands/request.js +59 -0
- package/dist/src/commands/search.d.ts +2 -0
- package/dist/src/commands/search.js +22 -0
- package/dist/src/config.d.ts +54 -0
- package/dist/src/config.js +356 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +4 -0
- package/dist/src/legacy.d.ts +8 -0
- package/dist/src/legacy.js +88 -0
- package/dist/src/project.d.ts +102 -0
- package/dist/src/project.js +475 -0
- package/dist/src/prompt.d.ts +4 -0
- package/dist/src/prompt.js +64 -0
- package/dist/src/runtime-view.d.ts +17 -0
- package/dist/src/runtime-view.js +80 -0
- package/dist/src/spinner.d.ts +14 -0
- package/dist/src/spinner.js +46 -0
- package/dist/src/types.d.ts +36 -0
- package/dist/src/types.js +1 -0
- package/dist/src/ui.d.ts +26 -0
- package/dist/src/ui.js +182 -0
- package/dist/src/utils.d.ts +10 -0
- package/dist/src/utils.js +86 -0
- package/package.json +32 -0
- package/tsconfig.build.json +15 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { AUTH_VALIDATION_TTL_MS, buildAuthValidationKey, DEFAULT_BASE_URL, isUuid, readAuthValidationState, resolveRuntimeConfig, saveAuthValidationState, } from "./config.js";
|
|
2
|
+
import { createSpinner } from "./spinner.js";
|
|
3
|
+
export class ApiError extends Error {
|
|
4
|
+
status;
|
|
5
|
+
data;
|
|
6
|
+
constructor(status, message, data) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'ApiError';
|
|
9
|
+
this.status = status;
|
|
10
|
+
this.data = data;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export class AuthenticationError extends ApiError {
|
|
14
|
+
constructor(status, message, data) {
|
|
15
|
+
super(status, message, data);
|
|
16
|
+
this.name = 'AuthenticationError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export class PermissionDeniedError extends ApiError {
|
|
20
|
+
path;
|
|
21
|
+
constructor(path, message, data) {
|
|
22
|
+
super(403, message, data);
|
|
23
|
+
this.name = 'PermissionDeniedError';
|
|
24
|
+
this.path = path;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const REQUEST_DELAY_MS = 50;
|
|
28
|
+
function normalizeBaseUrl(baseUrl) {
|
|
29
|
+
const url = new URL(baseUrl || DEFAULT_BASE_URL);
|
|
30
|
+
url.pathname = url.pathname.replace(/\/+$/, '');
|
|
31
|
+
return url;
|
|
32
|
+
}
|
|
33
|
+
function buildUrl(baseUrl, requestPath, query) {
|
|
34
|
+
if (/^https?:\/\//i.test(requestPath)) {
|
|
35
|
+
const absolute = new URL(requestPath);
|
|
36
|
+
if (query) {
|
|
37
|
+
for (const [key, value] of Object.entries(query)) {
|
|
38
|
+
if (value === undefined || value === null || value === '') {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (Array.isArray(value)) {
|
|
42
|
+
for (const item of value) {
|
|
43
|
+
absolute.searchParams.append(key, String(item));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
absolute.searchParams.set(key, String(value));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return absolute;
|
|
52
|
+
}
|
|
53
|
+
const url = normalizeBaseUrl(baseUrl);
|
|
54
|
+
const pathname = requestPath.startsWith('/') ? requestPath : `/${requestPath}`;
|
|
55
|
+
const basePath = url.pathname === '/' ? '' : url.pathname;
|
|
56
|
+
url.pathname = pathname.startsWith(basePath) ? pathname : `${basePath}${pathname}`;
|
|
57
|
+
if (query) {
|
|
58
|
+
for (const [key, value] of Object.entries(query)) {
|
|
59
|
+
if (value === undefined || value === null || value === '') {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (Array.isArray(value)) {
|
|
63
|
+
for (const item of value) {
|
|
64
|
+
url.searchParams.append(key, String(item));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
url.searchParams.set(key, String(value));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return url;
|
|
73
|
+
}
|
|
74
|
+
function normalizeToken(token) {
|
|
75
|
+
if (!token) {
|
|
76
|
+
return '';
|
|
77
|
+
}
|
|
78
|
+
return token.startsWith('Bearer ') ? token : `Bearer ${token}`;
|
|
79
|
+
}
|
|
80
|
+
function defaultSpinnerLabel(method, path) {
|
|
81
|
+
const upperMethod = method.toUpperCase();
|
|
82
|
+
if (path === '/api/v1/me') {
|
|
83
|
+
return 'Validating session';
|
|
84
|
+
}
|
|
85
|
+
if (path === '/api/v1/endpoints') {
|
|
86
|
+
return 'Loading endpoints';
|
|
87
|
+
}
|
|
88
|
+
if (path === '/api/v1/search') {
|
|
89
|
+
return 'Searching';
|
|
90
|
+
}
|
|
91
|
+
if (path === '/api/v1/component' && upperMethod === 'GET') {
|
|
92
|
+
return 'Loading components';
|
|
93
|
+
}
|
|
94
|
+
if (path === '/api/v1/component' && upperMethod === 'POST') {
|
|
95
|
+
return 'Creating component';
|
|
96
|
+
}
|
|
97
|
+
if (/^\/api\/v1\/component\/[^/]+\/save$/i.test(path)) {
|
|
98
|
+
return 'Saving component';
|
|
99
|
+
}
|
|
100
|
+
if (/^\/api\/v1\/component\/[^/]+$/i.test(path) && upperMethod === 'GET') {
|
|
101
|
+
return 'Loading component';
|
|
102
|
+
}
|
|
103
|
+
return `${upperMethod} ${path}`;
|
|
104
|
+
}
|
|
105
|
+
function toErrorMessage(responseText, status) {
|
|
106
|
+
const trimmed = responseText.trim();
|
|
107
|
+
return trimmed ? `${status} ${trimmed}` : `HTTP ${status}`;
|
|
108
|
+
}
|
|
109
|
+
function delay(ms) {
|
|
110
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
111
|
+
}
|
|
112
|
+
async function readResponseData(response) {
|
|
113
|
+
const text = await response.text();
|
|
114
|
+
const contentType = response.headers.get('content-type') || '';
|
|
115
|
+
if (!text) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
if (contentType.includes('application/json')) {
|
|
119
|
+
try {
|
|
120
|
+
return JSON.parse(text);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return text;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return text;
|
|
127
|
+
}
|
|
128
|
+
export class RevoClient {
|
|
129
|
+
baseUrl;
|
|
130
|
+
instance;
|
|
131
|
+
token;
|
|
132
|
+
fetchImpl;
|
|
133
|
+
constructor(options = {}) {
|
|
134
|
+
const config = resolveRuntimeConfig(options);
|
|
135
|
+
this.baseUrl = config.baseUrl;
|
|
136
|
+
this.instance = config.instance;
|
|
137
|
+
this.token = config.token;
|
|
138
|
+
this.fetchImpl = options.fetch || globalThis.fetch;
|
|
139
|
+
}
|
|
140
|
+
get authHeader() {
|
|
141
|
+
return normalizeToken(this.token);
|
|
142
|
+
}
|
|
143
|
+
get authValidationKey() {
|
|
144
|
+
return buildAuthValidationKey({
|
|
145
|
+
baseUrl: this.baseUrl,
|
|
146
|
+
instance: this.instance,
|
|
147
|
+
token: this.token,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
assertReady() {
|
|
151
|
+
if (!this.fetchImpl) {
|
|
152
|
+
throw new Error('Fetch is not available in this Node.js runtime.');
|
|
153
|
+
}
|
|
154
|
+
if (!this.token) {
|
|
155
|
+
throw new Error('Missing API token. Run `revo auth login` or set REVO_TOKEN / REVO_API_KEY.');
|
|
156
|
+
}
|
|
157
|
+
if (!this.instance) {
|
|
158
|
+
throw new Error('Missing instance UUID. Run `revo auth login` or set REVO_INSTANCE.');
|
|
159
|
+
}
|
|
160
|
+
if (!isUuid(this.instance)) {
|
|
161
|
+
throw new Error(`Invalid instance UUID: ${this.instance}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async validateSession(options = {}) {
|
|
165
|
+
this.assertReady();
|
|
166
|
+
const cached = readAuthValidationState(this.authValidationKey);
|
|
167
|
+
const now = Date.now();
|
|
168
|
+
if (!options.force
|
|
169
|
+
&& cached
|
|
170
|
+
&& cached.key === this.authValidationKey
|
|
171
|
+
&& now - cached.checkedAt < AUTH_VALIDATION_TTL_MS) {
|
|
172
|
+
if (cached.status === 'not_authenticated') {
|
|
173
|
+
throw new AuthenticationError(401, 'Not authenticated. Run `revo auth login`.', null);
|
|
174
|
+
}
|
|
175
|
+
if (cached.profile !== undefined) {
|
|
176
|
+
return {
|
|
177
|
+
authenticated: true,
|
|
178
|
+
fromCache: true,
|
|
179
|
+
profile: cached.profile,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
try {
|
|
184
|
+
const profile = await this.requestData('GET', '/api/v1/me', { authGuard: false });
|
|
185
|
+
saveAuthValidationState({
|
|
186
|
+
key: this.authValidationKey,
|
|
187
|
+
status: 'authenticated',
|
|
188
|
+
checkedAt: now,
|
|
189
|
+
profile,
|
|
190
|
+
});
|
|
191
|
+
return {
|
|
192
|
+
authenticated: true,
|
|
193
|
+
fromCache: false,
|
|
194
|
+
profile,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
if (error instanceof ApiError && error.status === 401) {
|
|
199
|
+
saveAuthValidationState({
|
|
200
|
+
key: this.authValidationKey,
|
|
201
|
+
status: 'not_authenticated',
|
|
202
|
+
checkedAt: now,
|
|
203
|
+
});
|
|
204
|
+
throw new AuthenticationError(401, 'Not authenticated. Run `revo auth login`.', error.data);
|
|
205
|
+
}
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async request(method, path, options = {}) {
|
|
210
|
+
this.assertReady();
|
|
211
|
+
if (options.authGuard !== false && path !== '/api/v1/me') {
|
|
212
|
+
await this.validateSession();
|
|
213
|
+
}
|
|
214
|
+
const url = buildUrl(this.baseUrl, path, options.query);
|
|
215
|
+
const headers = new Headers(options.headers || {});
|
|
216
|
+
headers.set('Authorization', this.authHeader);
|
|
217
|
+
headers.set('x-api-key', this.token);
|
|
218
|
+
headers.set('instance', this.instance);
|
|
219
|
+
headers.set('x-api-instance', this.instance);
|
|
220
|
+
let body = options.body;
|
|
221
|
+
if (body !== undefined && body !== null && method.toUpperCase() !== 'GET') {
|
|
222
|
+
headers.set('Content-Type', 'application/json');
|
|
223
|
+
body = JSON.stringify(body);
|
|
224
|
+
}
|
|
225
|
+
else if (method.toUpperCase() === 'GET') {
|
|
226
|
+
body = undefined;
|
|
227
|
+
}
|
|
228
|
+
const spinner = createSpinner({
|
|
229
|
+
text: options.spinnerLabel || defaultSpinnerLabel(method, path),
|
|
230
|
+
});
|
|
231
|
+
spinner.start();
|
|
232
|
+
try {
|
|
233
|
+
await delay(REQUEST_DELAY_MS);
|
|
234
|
+
const response = await this.fetchImpl(url, {
|
|
235
|
+
method: method.toUpperCase(),
|
|
236
|
+
headers,
|
|
237
|
+
body: body,
|
|
238
|
+
});
|
|
239
|
+
const data = await readResponseData(response);
|
|
240
|
+
if (!response.ok && response.status !== 304) {
|
|
241
|
+
if (response.status === 401) {
|
|
242
|
+
saveAuthValidationState({
|
|
243
|
+
key: this.authValidationKey,
|
|
244
|
+
status: 'not_authenticated',
|
|
245
|
+
checkedAt: Date.now(),
|
|
246
|
+
});
|
|
247
|
+
throw new AuthenticationError(401, 'Not authenticated. Run `revo auth login`.', data);
|
|
248
|
+
}
|
|
249
|
+
if (response.status === 403 && path !== '/api/v1/me') {
|
|
250
|
+
throw new PermissionDeniedError(path, `Access denied for ${path}.`, data);
|
|
251
|
+
}
|
|
252
|
+
throw new ApiError(response.status, toErrorMessage(typeof data === 'string' ? data : JSON.stringify(data), response.status), data);
|
|
253
|
+
}
|
|
254
|
+
const previous = readAuthValidationState(this.authValidationKey);
|
|
255
|
+
saveAuthValidationState({
|
|
256
|
+
key: this.authValidationKey,
|
|
257
|
+
status: 'authenticated',
|
|
258
|
+
checkedAt: Date.now(),
|
|
259
|
+
profile: previous && previous.key === this.authValidationKey ? previous.profile : undefined,
|
|
260
|
+
});
|
|
261
|
+
return {
|
|
262
|
+
status: response.status,
|
|
263
|
+
ok: response.ok,
|
|
264
|
+
data,
|
|
265
|
+
headers: response.headers,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
finally {
|
|
269
|
+
spinner.stop();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
async requestData(method, path, options = {}) {
|
|
273
|
+
const response = await this.request(method, path, options);
|
|
274
|
+
return response.data;
|
|
275
|
+
}
|
|
276
|
+
async me(options = {}) {
|
|
277
|
+
const result = await this.validateSession({ force: options.force });
|
|
278
|
+
if (result.profile !== undefined) {
|
|
279
|
+
return result.profile;
|
|
280
|
+
}
|
|
281
|
+
return this.requestData('GET', '/api/v1/me', { authGuard: false });
|
|
282
|
+
}
|
|
283
|
+
async listEndpoints() {
|
|
284
|
+
return this.requestData('GET', '/api/v1/endpoints');
|
|
285
|
+
}
|
|
286
|
+
async getEditorTypes(requestPath = '/v1/editor/types') {
|
|
287
|
+
return this.requestData('GET', requestPath, {
|
|
288
|
+
spinnerLabel: 'Loading editor types',
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
async debugComponent(requestPath, body) {
|
|
292
|
+
return this.requestData('POST', requestPath, {
|
|
293
|
+
body,
|
|
294
|
+
spinnerLabel: 'Debugging component',
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
async search(params) {
|
|
298
|
+
return this.requestData('GET', '/api/v1/search', { query: params });
|
|
299
|
+
}
|
|
300
|
+
async listComponents(options = {}) {
|
|
301
|
+
return this.requestData('GET', options.path || '/api/v1/component', {
|
|
302
|
+
query: options.query,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
async getComponent(componentId) {
|
|
306
|
+
return this.requestData('GET', `/api/v1/component/${componentId}`);
|
|
307
|
+
}
|
|
308
|
+
async createComponent(body) {
|
|
309
|
+
return this.request('POST', '/api/v1/component', { body });
|
|
310
|
+
}
|
|
311
|
+
async saveComponentElements(componentId, body) {
|
|
312
|
+
return this.request('POST', `/api/v1/component/${componentId}/save`, { body });
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
export { buildUrl, normalizeToken };
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { clearAuthValidationState, clearStoredConfig, getConfigDir, isUuid, loadStoredConfig, resolveRuntimeConfig, saveStoredConfig, } from "../config.js";
|
|
3
|
+
import { readLegacyImportFromFile } from "../legacy.js";
|
|
4
|
+
import { promptText } from "../prompt.js";
|
|
5
|
+
import { buildRuntimeViewModel } from "../runtime-view.js";
|
|
6
|
+
import { renderAuthStatus } from "../ui.js";
|
|
7
|
+
import { readBoolFlag, readFlag } from "../utils.js";
|
|
8
|
+
function getConfigInput(args) {
|
|
9
|
+
return resolveRuntimeConfig({
|
|
10
|
+
baseUrl: readFlag(args, ['url', 'baseUrl']) || '',
|
|
11
|
+
instance: readFlag(args, ['instance', 'i']) || '',
|
|
12
|
+
token: readFlag(args, ['token', 't']) || '',
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
async function promptMissingConfig(runtime) {
|
|
16
|
+
let token = runtime.token;
|
|
17
|
+
let instance = runtime.instance;
|
|
18
|
+
if (!token) {
|
|
19
|
+
token = (await promptText('Paste API key (input is visible)')).trim();
|
|
20
|
+
}
|
|
21
|
+
if (!instance) {
|
|
22
|
+
instance = (await promptText('Paste instance ID')).trim();
|
|
23
|
+
}
|
|
24
|
+
if (!token) {
|
|
25
|
+
throw new Error('API key is required.');
|
|
26
|
+
}
|
|
27
|
+
if (!instance || !isUuid(instance)) {
|
|
28
|
+
throw new Error('Instance ID must be a UUID.');
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
token,
|
|
32
|
+
instance,
|
|
33
|
+
baseUrl: runtime.baseUrl,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export async function handleAuthCommand(context) {
|
|
37
|
+
const { args, client, print, println, error } = context;
|
|
38
|
+
const subcommand = args._[1] || 'login';
|
|
39
|
+
if (subcommand === 'logout') {
|
|
40
|
+
clearStoredConfig();
|
|
41
|
+
println(`Removed stored credentials from ${getConfigDir()}.`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (subcommand === 'get') {
|
|
45
|
+
const stored = loadStoredConfig();
|
|
46
|
+
print({
|
|
47
|
+
instance: stored.instance || null,
|
|
48
|
+
baseUrl: stored.baseUrl,
|
|
49
|
+
tokenConfigured: Boolean(stored.token),
|
|
50
|
+
configDir: getConfigDir(),
|
|
51
|
+
});
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (subcommand === 'set') {
|
|
55
|
+
const instance = readFlag(args, ['instance', 'i']);
|
|
56
|
+
if (!instance) {
|
|
57
|
+
throw new Error('Missing instance UUID.');
|
|
58
|
+
}
|
|
59
|
+
if (!isUuid(instance)) {
|
|
60
|
+
throw new Error('Instance must be a UUID.');
|
|
61
|
+
}
|
|
62
|
+
const stored = loadStoredConfig();
|
|
63
|
+
saveStoredConfig({
|
|
64
|
+
baseUrl: stored.baseUrl,
|
|
65
|
+
token: stored.token,
|
|
66
|
+
instance,
|
|
67
|
+
});
|
|
68
|
+
println(`Saved default instance ${instance}.`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (subcommand === 'status') {
|
|
72
|
+
const view = await buildRuntimeViewModel(context, { forceValidation: true });
|
|
73
|
+
print(renderAuthStatus(view));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (subcommand === 'key') {
|
|
77
|
+
const filePath = readFlag(args, ['file', 'f']) || path.join(process.cwd(), 'config.json');
|
|
78
|
+
const selectedInstance = readFlag(args, ['instance', 'i']) || '';
|
|
79
|
+
const offline = readBoolFlag(args, ['offline', 'o']);
|
|
80
|
+
const imported = readLegacyImportFromFile(filePath, selectedInstance || undefined);
|
|
81
|
+
if (!imported) {
|
|
82
|
+
throw new Error(`Unable to read legacy config from ${filePath}.`);
|
|
83
|
+
}
|
|
84
|
+
saveStoredConfig(imported);
|
|
85
|
+
clearAuthValidationState();
|
|
86
|
+
println(`Imported legacy config from ${filePath}.`);
|
|
87
|
+
if (!offline) {
|
|
88
|
+
client.token = imported.token;
|
|
89
|
+
client.instance = imported.instance;
|
|
90
|
+
client.baseUrl = imported.baseUrl;
|
|
91
|
+
if (imported.token && imported.instance) {
|
|
92
|
+
try {
|
|
93
|
+
await client.validateSession({ force: true });
|
|
94
|
+
}
|
|
95
|
+
catch (validationError) {
|
|
96
|
+
if (validationError instanceof Error) {
|
|
97
|
+
error(`Saved config, but validation failed: ${validationError.message}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (subcommand !== 'login') {
|
|
105
|
+
throw new Error('Unknown auth command.');
|
|
106
|
+
}
|
|
107
|
+
const stored = loadStoredConfig();
|
|
108
|
+
if (stored.token && stored.instance) {
|
|
109
|
+
println(`Warning: already logged in for instance ${stored.instance}. Run \`revo auth logout\` before logging in again.`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const runtime = getConfigInput(args);
|
|
113
|
+
const next = runtime.token && runtime.instance ? runtime : await promptMissingConfig(runtime);
|
|
114
|
+
client.token = next.token;
|
|
115
|
+
client.instance = next.instance;
|
|
116
|
+
client.baseUrl = next.baseUrl;
|
|
117
|
+
try {
|
|
118
|
+
await client.validateSession({ force: true });
|
|
119
|
+
}
|
|
120
|
+
catch (validationError) {
|
|
121
|
+
if (validationError instanceof Error) {
|
|
122
|
+
error(`Saved credentials, but validation failed: ${validationError.message}`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
error(`Saved credentials, but validation failed: ${String(validationError)}`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
saveStoredConfig(next);
|
|
129
|
+
clearAuthValidationState();
|
|
130
|
+
println(`Logged in and saved credentials to ${getConfigDir()}.`);
|
|
131
|
+
}
|