@vention/vention-cli 0.12.2 → 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 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 res = await fetch(`${digitalTwinUrl}/v2/library`, {
238
- method: "GET",
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 res = await fetch(`${digitalTwinUrl}/v1/library`, {
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: JSON.stringify(application),
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 res = await fetch(`${baseUrl}/api/v3/digital_twin_infrastructure/allocations`, {
305
- method: "GET",
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.name("vention").description("CLI tool for Vention").version("0.2.0");
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
- console.log();
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
- console.log();
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
- console.log();
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.12.2",
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.23.8"
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<void>;
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,3 @@
1
+ export declare const setVerbose: (enabled: boolean) => void;
2
+ export declare const isVerbose: () => boolean;
3
+ export declare const verboseLog: (message: string) => void;
@@ -0,0 +1 @@
1
+ export {};