@vention/vention-cli 0.12.1 → 0.13.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/cli.esm.js +160 -35
- package/package.json +2 -2
- package/src/digital-twin-client.d.ts +1 -1
- package/src/error-handler.d.ts +9 -0
- package/src/error-handler.test.d.ts +1 -0
- package/src/fetch-helper.d.ts +8 -0
- package/src/fetch-helper.test.d.ts +1 -0
- package/src/verbose-logger.d.ts +3 -0
- package/src/verbose-logger.test.d.ts +1 -0
package/cli.esm.js
CHANGED
|
@@ -10,6 +10,7 @@ import { URLSearchParams, fileURLToPath } from 'url';
|
|
|
10
10
|
import { once } from 'events';
|
|
11
11
|
import crypto from 'crypto';
|
|
12
12
|
import { z } from 'zod';
|
|
13
|
+
import { createRequire } from 'module';
|
|
13
14
|
|
|
14
15
|
const getConfigPath = () => {
|
|
15
16
|
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
@@ -217,6 +218,132 @@ const loginWithSso = async (environment) => {
|
|
|
217
218
|
};
|
|
218
219
|
const getSsoEnvironmentConfig = getSsoConfig;
|
|
219
220
|
|
|
221
|
+
let verboseEnabled = false;
|
|
222
|
+
const setVerbose = (enabled) => {
|
|
223
|
+
verboseEnabled = enabled;
|
|
224
|
+
};
|
|
225
|
+
const isVerbose = () => verboseEnabled;
|
|
226
|
+
const verboseLog = (message) => {
|
|
227
|
+
if (verboseEnabled) {
|
|
228
|
+
console.log(chalk.dim("[verbose]"), message);
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
class ApiError extends Error {
|
|
233
|
+
constructor(message, status, statusText, responseBody, url) {
|
|
234
|
+
super(message);
|
|
235
|
+
this.status = status;
|
|
236
|
+
this.statusText = statusText;
|
|
237
|
+
this.responseBody = responseBody;
|
|
238
|
+
this.url = url;
|
|
239
|
+
this.name = "ApiError";
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
async function assertApiResponseOk(res, url) {
|
|
243
|
+
if (!res.ok) {
|
|
244
|
+
let responseBody;
|
|
245
|
+
let text = "";
|
|
246
|
+
try {
|
|
247
|
+
text = await res.text();
|
|
248
|
+
responseBody = text ? JSON.parse(text) : null;
|
|
249
|
+
}
|
|
250
|
+
catch (_a) {
|
|
251
|
+
responseBody = text || null;
|
|
252
|
+
}
|
|
253
|
+
throw new ApiError(`Request failed: ${res.status} ${res.statusText}`, res.status, res.statusText, responseBody, url);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const ERROR_SUGGESTIONS = {
|
|
257
|
+
400: {
|
|
258
|
+
default: ["The request was invalid. Check the server response above for details."],
|
|
259
|
+
byEndpoint: {
|
|
260
|
+
library: [
|
|
261
|
+
"The application data may be invalid or corrupted.",
|
|
262
|
+
"Try running 'vention unlink' and then 'vention link' to re-link the application.",
|
|
263
|
+
],
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
401: {
|
|
267
|
+
default: ["Authentication error. Try running 'vention login' to re-authenticate."],
|
|
268
|
+
},
|
|
269
|
+
403: {
|
|
270
|
+
default: ["Authentication error. Try running 'vention login' to re-authenticate."],
|
|
271
|
+
},
|
|
272
|
+
404: {
|
|
273
|
+
default: ["The requested resource was not found."],
|
|
274
|
+
byEndpoint: {
|
|
275
|
+
allocations: ["No active session found for this design.", "Make sure the design is open in your browser with a live session."],
|
|
276
|
+
library: [
|
|
277
|
+
"The application was not found on the digital twin.",
|
|
278
|
+
"The application may have been deleted or the session may have expired.",
|
|
279
|
+
"Try refreshing the design in your browser and running 'vention link' again.",
|
|
280
|
+
],
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
413: {
|
|
284
|
+
default: ["The payload is too large."],
|
|
285
|
+
byEndpoint: {
|
|
286
|
+
library: [
|
|
287
|
+
"The application is too large to push.",
|
|
288
|
+
"Remove these from the application directory before pushing again:",
|
|
289
|
+
" - .venv or venv (Python virtual environment)",
|
|
290
|
+
" - node_modules (Node.js dependencies)",
|
|
291
|
+
" - Large data files or binary assets",
|
|
292
|
+
],
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
500: {
|
|
296
|
+
default: ["The server encountered an error.", "This is likely a temporary issue. Please try again in a few moments."],
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
const ENDPOINT_PATTERNS = [
|
|
300
|
+
["allocations", "/allocations"],
|
|
301
|
+
["library", "/library"],
|
|
302
|
+
];
|
|
303
|
+
const getEndpointFromUrl = (url) => { var _a; return (_a = ENDPOINT_PATTERNS.find(([, pattern]) => url.includes(pattern))) === null || _a === void 0 ? void 0 : _a[0]; };
|
|
304
|
+
const getErrorSuggestions = (status, url) => {
|
|
305
|
+
var _a, _b, _c;
|
|
306
|
+
const entry = (_a = ERROR_SUGGESTIONS[status]) !== null && _a !== void 0 ? _a : (status >= 500 ? ERROR_SUGGESTIONS[500] : undefined);
|
|
307
|
+
if (!entry)
|
|
308
|
+
return [];
|
|
309
|
+
const endpoint = getEndpointFromUrl(url);
|
|
310
|
+
return (_c = (endpoint && ((_b = entry.byEndpoint) === null || _b === void 0 ? void 0 : _b[endpoint]))) !== null && _c !== void 0 ? _c : entry.default;
|
|
311
|
+
};
|
|
312
|
+
const handleCommandError = (error, context) => {
|
|
313
|
+
console.error(chalk.red(`${context}:`));
|
|
314
|
+
if (error instanceof ApiError) {
|
|
315
|
+
console.error(chalk.red(`HTTP ${error.status}: ${error.statusText}`));
|
|
316
|
+
if (error.responseBody) {
|
|
317
|
+
console.error(chalk.yellow("Server response:"));
|
|
318
|
+
console.error(chalk.dim(JSON.stringify(error.responseBody, null, 2)));
|
|
319
|
+
}
|
|
320
|
+
if (isVerbose()) {
|
|
321
|
+
console.error(chalk.dim(`URL: ${error.url}`));
|
|
322
|
+
}
|
|
323
|
+
const suggestions = getErrorSuggestions(error.status, error.url);
|
|
324
|
+
if (suggestions.length > 0) {
|
|
325
|
+
console.error();
|
|
326
|
+
suggestions.forEach(msg => console.error(chalk.yellow(msg)));
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
console.error(chalk.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
331
|
+
}
|
|
332
|
+
process.exit(1);
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
const fetchJson = async (url, options = {}) => {
|
|
336
|
+
const { method = "GET", headers = {}, body } = options;
|
|
337
|
+
verboseLog(`${method} ${url}`);
|
|
338
|
+
const res = await fetch(url, {
|
|
339
|
+
method,
|
|
340
|
+
headers,
|
|
341
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
342
|
+
});
|
|
343
|
+
await assertApiResponseOk(res, url);
|
|
344
|
+
return await res.json();
|
|
345
|
+
};
|
|
346
|
+
|
|
220
347
|
const getDigitalTwinUrl = (environment, sessionId) => {
|
|
221
348
|
switch (environment) {
|
|
222
349
|
case "local":
|
|
@@ -234,17 +361,13 @@ const getAllApplicationsWithSourceCode = async (environment, designId, sessionId
|
|
|
234
361
|
throw new Error("Linked session token is not set");
|
|
235
362
|
}
|
|
236
363
|
const digitalTwinUrl = getDigitalTwinUrl(environment, sessionId);
|
|
237
|
-
const
|
|
238
|
-
|
|
364
|
+
const url = `${digitalTwinUrl}/v2/library`;
|
|
365
|
+
const response = await fetchJson(url, {
|
|
239
366
|
headers: {
|
|
240
367
|
Accept: "application/json",
|
|
241
368
|
Cookie: `digital-twin-session-${designId}=s%3A${sessionId}.s`,
|
|
242
369
|
},
|
|
243
370
|
});
|
|
244
|
-
if (!res.ok) {
|
|
245
|
-
throw new Error(`Request failed: ${res.status} ${res.statusText}`);
|
|
246
|
-
}
|
|
247
|
-
const response = await res.json();
|
|
248
371
|
return response.applications || [];
|
|
249
372
|
};
|
|
250
373
|
const pushApplicationToDigitalTwin = async (environment, designId, sessionId, application) => {
|
|
@@ -252,19 +375,16 @@ const pushApplicationToDigitalTwin = async (environment, designId, sessionId, ap
|
|
|
252
375
|
throw new Error("Linked session token is not set");
|
|
253
376
|
}
|
|
254
377
|
const digitalTwinUrl = getDigitalTwinUrl(environment, sessionId);
|
|
255
|
-
const
|
|
378
|
+
const url = `${digitalTwinUrl}/v1/library`;
|
|
379
|
+
return await fetchJson(url, {
|
|
256
380
|
method: "PUT",
|
|
257
381
|
headers: {
|
|
258
382
|
"Content-Type": "application/json",
|
|
259
383
|
Accept: "application/json",
|
|
260
384
|
Cookie: `digital-twin-session-${designId}=s%3A${sessionId}.s`,
|
|
261
385
|
},
|
|
262
|
-
body:
|
|
386
|
+
body: application,
|
|
263
387
|
});
|
|
264
|
-
if (!res.ok) {
|
|
265
|
-
throw new Error(`Request failed: ${res.status} ${res.statusText}`);
|
|
266
|
-
}
|
|
267
|
-
return await res.json();
|
|
268
388
|
};
|
|
269
389
|
|
|
270
390
|
const STATE_FILENAME = ".machine-code-app-directory-info.json";
|
|
@@ -301,15 +421,8 @@ const getBaseRailsUrl = (environment) => {
|
|
|
301
421
|
const getUserAllocations = async (session) => {
|
|
302
422
|
const baseUrl = getBaseRailsUrl(session.environment);
|
|
303
423
|
const headers = await getAuthHeaders(session);
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
headers,
|
|
307
|
-
});
|
|
308
|
-
if (!res.ok) {
|
|
309
|
-
throw new Error(`Request failed: ${res.status} ${res.statusText}`);
|
|
310
|
-
}
|
|
311
|
-
const response = await res.json();
|
|
312
|
-
return response;
|
|
424
|
+
const url = `${baseUrl}/api/v3/digital_twin_infrastructure/allocations`;
|
|
425
|
+
return await fetchJson(url, { headers });
|
|
313
426
|
};
|
|
314
427
|
const getAuthHeaders = async (session) => {
|
|
315
428
|
var _a;
|
|
@@ -342,7 +455,7 @@ const getAuthHeaders = async (session) => {
|
|
|
342
455
|
};
|
|
343
456
|
|
|
344
457
|
const getDefaultIgnorePatterns = () => {
|
|
345
|
-
return new Set(["venv", "node_modules", "dist", "build", "__pycache__", /\.egg-info$/, ".machine-code-app-directory-info.json"]);
|
|
458
|
+
return new Set(["venv", ".venv", "node_modules", "dist", "build", "__pycache__", /\.egg-info$/, ".machine-code-app-directory-info.json"]);
|
|
346
459
|
};
|
|
347
460
|
const shouldIgnoreFileSystemNode = (nodeName, ignoredNodes) => {
|
|
348
461
|
for (const pattern of ignoredNodes) {
|
|
@@ -540,8 +653,29 @@ const selectDesignAndApp = async (session) => {
|
|
|
540
653
|
};
|
|
541
654
|
};
|
|
542
655
|
|
|
656
|
+
const require$1 = createRequire(import.meta.url);
|
|
657
|
+
function loadPackageJson() {
|
|
658
|
+
try {
|
|
659
|
+
return require$1("./package.json");
|
|
660
|
+
}
|
|
661
|
+
catch (_a) {
|
|
662
|
+
return require$1("../package.json");
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
const packageJson = loadPackageJson();
|
|
543
666
|
const program = new Command();
|
|
544
|
-
program
|
|
667
|
+
program
|
|
668
|
+
.name("vention")
|
|
669
|
+
.description("CLI tool for Vention")
|
|
670
|
+
.version(packageJson.version)
|
|
671
|
+
.option("--verbose", "Enable verbose output for debugging")
|
|
672
|
+
.hook("preAction", thisCommand => {
|
|
673
|
+
const opts = thisCommand.opts();
|
|
674
|
+
setVerbose(opts.verbose === true);
|
|
675
|
+
})
|
|
676
|
+
.hook("postAction", () => {
|
|
677
|
+
setVerbose(false);
|
|
678
|
+
});
|
|
545
679
|
const ventionLogo = `
|
|
546
680
|
╔════════════════════════════════════════════════════════════════════════════════════════════════════════╗
|
|
547
681
|
║ ║
|
|
@@ -629,10 +763,7 @@ program
|
|
|
629
763
|
console.log(chalk.blue("You can now use 'vention pull' to download app content or 'vention push' to upload local changes."));
|
|
630
764
|
}
|
|
631
765
|
catch (error) {
|
|
632
|
-
|
|
633
|
-
console.error(chalk.red("Failed to link directory:"));
|
|
634
|
-
console.error(chalk.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
635
|
-
process.exit(1);
|
|
766
|
+
handleCommandError(error, "Failed to link directory");
|
|
636
767
|
}
|
|
637
768
|
});
|
|
638
769
|
program
|
|
@@ -708,10 +839,7 @@ program
|
|
|
708
839
|
console.log(chalk.blue("💡 Your changes have been uploaded!"));
|
|
709
840
|
}
|
|
710
841
|
catch (error) {
|
|
711
|
-
|
|
712
|
-
console.error(chalk.red("Failed to push application:"));
|
|
713
|
-
console.error(chalk.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
714
|
-
process.exit(1);
|
|
842
|
+
handleCommandError(error, "Failed to push application");
|
|
715
843
|
}
|
|
716
844
|
});
|
|
717
845
|
program
|
|
@@ -736,10 +864,7 @@ program
|
|
|
736
864
|
}
|
|
737
865
|
}
|
|
738
866
|
catch (error) {
|
|
739
|
-
|
|
740
|
-
console.error(chalk.red("Failed to pull application:"));
|
|
741
|
-
console.error(chalk.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
|
|
742
|
-
process.exit(1);
|
|
867
|
+
handleCommandError(error, "Failed to pull application");
|
|
743
868
|
}
|
|
744
869
|
});
|
|
745
870
|
const resolvedArgv = realpathSync(process.argv[1]);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vention/vention-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "CLI tool for Vention applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"chalk": "4.1.2",
|
|
24
24
|
"commander": "14.0.2",
|
|
25
25
|
"@inquirer/prompts": "7.6.0",
|
|
26
|
-
"zod": "3.
|
|
26
|
+
"zod": "3.25.76"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@nx/vite": "21.1.3",
|
|
@@ -11,4 +11,4 @@ export interface Application {
|
|
|
11
11
|
}
|
|
12
12
|
export declare const getDigitalTwinUrl: (environment: SessionData["environment"], sessionId: string) => string;
|
|
13
13
|
export declare const getAllApplicationsWithSourceCode: (environment: SessionData["environment"], designId: number, sessionId: string) => Promise<Application[]>;
|
|
14
|
-
export declare const pushApplicationToDigitalTwin: (environment: SessionData["environment"], designId: number, sessionId: string, application: Application) => Promise<
|
|
14
|
+
export declare const pushApplicationToDigitalTwin: <T = unknown>(environment: SessionData["environment"], designId: number, sessionId: string, application: Application) => Promise<T>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare class ApiError extends Error {
|
|
2
|
+
readonly status: number;
|
|
3
|
+
readonly statusText: string;
|
|
4
|
+
readonly responseBody: unknown;
|
|
5
|
+
readonly url: string;
|
|
6
|
+
constructor(message: string, status: number, statusText: string, responseBody: unknown, url: string);
|
|
7
|
+
}
|
|
8
|
+
export declare function assertApiResponseOk(res: Response, url: string): Promise<void>;
|
|
9
|
+
export declare const handleCommandError: (error: unknown, context: string) => never;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
2
|
+
interface FetchOptions {
|
|
3
|
+
method?: HttpMethod;
|
|
4
|
+
headers?: Record<string, string>;
|
|
5
|
+
body?: unknown;
|
|
6
|
+
}
|
|
7
|
+
export declare const fetchJson: <T>(url: string, options?: FetchOptions) => Promise<T>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|