@vention/vention-cli 0.24.2 → 0.24.3

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 (2) hide show
  1. package/cli.esm.js +57 -16
  2. package/package.json +1 -1
package/cli.esm.js CHANGED
@@ -6008,6 +6008,9 @@ const TokenResponseSchema = z.object({
6008
6008
  const openBrowser = url => {
6009
6009
  const platform = process.platform;
6010
6010
  const command = platform === "darwin" ? `open "${url}"` : platform === "win32" ? `start "${url}"` : `xdg-open "${url}"`;
6011
+
6012
+ // URL is constructed from trusted constants and internal params (not user input)
6013
+ // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process
6011
6014
  exec$9(command, error => {
6012
6015
  if (error) {
6013
6016
  console.log("Please open this URL in your browser:", url);
@@ -6027,6 +6030,9 @@ const startCallbackServer = async expectedState => {
6027
6030
  const server = http.createServer();
6028
6031
  const waitForCode = (async () => {
6029
6032
  try {
6033
+ // Keep listening until we get the expected /callback request
6034
+ // This avoids external mutable resolve/reject handlers.
6035
+ // eslint-disable-next-line no-constant-condition
6030
6036
  while (true) {
6031
6037
  const [req, res] = await once(server, "request");
6032
6038
  const url = new URL(req.url || "/", `http://127.0.0.1`);
@@ -6088,7 +6094,7 @@ const startCallbackServer = async expectedState => {
6088
6094
  const getSsoConfig = environment => {
6089
6095
  if (environment === "local") throw new Error("SSO is not supported for local environment. Use cookie-based login.");
6090
6096
  const ssoConfig = CONNECTED_APP_SSO_CONFIG[environment];
6091
- if (!(ssoConfig === null || ssoConfig === void 0 ? void 0 : ssoConfig.clientId)) throw new Error("Missing CONNECTED_APP clientId for selected environment in sso-constants.ts");
6097
+ if (!(ssoConfig != null && ssoConfig.clientId)) throw new Error("Missing CONNECTED_APP clientId for selected environment in sso-constants.ts");
6092
6098
  if (!ssoConfig.tokenUrl) throw new Error("Missing tokenUrl for selected environment in sso-constants.ts");
6093
6099
  return {
6094
6100
  clientId: ssoConfig.clientId,
@@ -6124,7 +6130,7 @@ const exchangeAuthorizationCodeForTokens = async (config, code, codeVerifier, re
6124
6130
  };
6125
6131
  };
6126
6132
  const refreshAccessToken = async (config, refreshToken) => {
6127
- var _a;
6133
+ var _parsed$refresh_token;
6128
6134
  const body = new URLSearchParams$2({
6129
6135
  grant_type: "refresh_token",
6130
6136
  refresh_token: refreshToken,
@@ -6144,7 +6150,7 @@ const refreshAccessToken = async (config, refreshToken) => {
6144
6150
  const parsed = TokenResponseSchema.parse(json);
6145
6151
  return {
6146
6152
  accessToken: parsed.access_token,
6147
- refreshToken: (_a = parsed.refresh_token) !== null && _a !== void 0 ? _a : refreshToken,
6153
+ refreshToken: (_parsed$refresh_token = parsed.refresh_token) != null ? _parsed$refresh_token : refreshToken,
6148
6154
  expiresInSeconds: parsed.expires_in
6149
6155
  };
6150
6156
  };
@@ -6158,6 +6164,7 @@ const loginWithSso = async environment => {
6158
6164
  verifier,
6159
6165
  challenge
6160
6166
  } = generatePkce();
6167
+ // OAuth 2.0 state: per-request nonce used to prevent CSRF and bind the callback to this initiation
6161
6168
  const state = generateRandomBase64Url(16);
6162
6169
  const {
6163
6170
  port,
@@ -6341,7 +6348,7 @@ async function assertApiResponseOk(res, url) {
6341
6348
  try {
6342
6349
  text = await res.text();
6343
6350
  responseBody = text ? JSON.parse(text) : null;
6344
- } catch (_a) {
6351
+ } catch (_unused) {
6345
6352
  responseBody = text || null;
6346
6353
  }
6347
6354
  throw new ApiError(`Request failed: ${res.status} ${res.statusText}`, res.status, res.statusText, responseBody, url);
@@ -6379,15 +6386,15 @@ const ERROR_SUGGESTIONS = {
6379
6386
  };
6380
6387
  const ENDPOINT_PATTERNS = [["allocations", "/allocations"], ["library", "/library"]];
6381
6388
  const getEndpointFromUrl = url => {
6382
- var _a;
6383
- return (_a = ENDPOINT_PATTERNS.find(([, pattern]) => url.includes(pattern))) === null || _a === void 0 ? void 0 : _a[0];
6389
+ var _ENDPOINT_PATTERNS$fi;
6390
+ return (_ENDPOINT_PATTERNS$fi = ENDPOINT_PATTERNS.find(([, pattern]) => url.includes(pattern))) == null ? void 0 : _ENDPOINT_PATTERNS$fi[0];
6384
6391
  };
6385
6392
  const getErrorSuggestions = (status, url) => {
6386
- var _a, _b, _c;
6387
- const entry = (_a = ERROR_SUGGESTIONS[status]) !== null && _a !== void 0 ? _a : status >= 500 ? ERROR_SUGGESTIONS[500] : undefined;
6393
+ var _ERROR_SUGGESTIONS$st, _ref, _entry$byEndpoint;
6394
+ const entry = (_ERROR_SUGGESTIONS$st = ERROR_SUGGESTIONS[status]) != null ? _ERROR_SUGGESTIONS$st : status >= 500 ? ERROR_SUGGESTIONS[500] : undefined;
6388
6395
  if (!entry) return [];
6389
6396
  const endpoint = getEndpointFromUrl(url);
6390
- return (_c = endpoint && ((_b = entry.byEndpoint) === null || _b === void 0 ? void 0 : _b[endpoint])) !== null && _c !== void 0 ? _c : entry.default;
6397
+ return (_ref = endpoint && ((_entry$byEndpoint = entry.byEndpoint) == null ? void 0 : _entry$byEndpoint[endpoint])) != null ? _ref : entry.default;
6391
6398
  };
6392
6399
  const handleCommandError = (error, context) => {
6393
6400
  console.error(chalk.red(`${context}:`));
@@ -6430,6 +6437,7 @@ const fetchJson = async (url, options = {}) => {
6430
6437
  const getDigitalTwinUrl = (environment, sessionId) => {
6431
6438
  switch (environment) {
6432
6439
  case "local":
6440
+ // This is where execution-engine-cad is served on vention-stack
6433
6441
  return `http://localhost:3101`;
6434
6442
  case "demo":
6435
6443
  return `https://digital-twin.vention.foo/digital-twin/machine-motion/passthrough/${sessionId}/80`;
@@ -6751,7 +6759,7 @@ const getUserAllocations = async session => {
6751
6759
  });
6752
6760
  };
6753
6761
  const getAuthHeaders = async session => {
6754
- var _a;
6762
+ var _session$oauth;
6755
6763
  const baseUrl = getBaseRailsUrl(session.environment);
6756
6764
  const baseHeaders = {
6757
6765
  Accept: "application/json",
@@ -6762,11 +6770,11 @@ const getAuthHeaders = async session => {
6762
6770
  if (!session.ventionSession) {
6763
6771
  throw new Error("Local environment requires vention_session. Please run 'vention login' and provide the cookie value.");
6764
6772
  }
6765
- return Object.assign(Object.assign({}, baseHeaders), {
6773
+ return Object.assign({}, baseHeaders, {
6766
6774
  Cookie: `vention_session=${session.ventionSession}`
6767
6775
  });
6768
6776
  }
6769
- if (!((_a = session.oauth) === null || _a === void 0 ? void 0 : _a.accessToken)) {
6777
+ if (!((_session$oauth = session.oauth) != null && _session$oauth.accessToken)) {
6770
6778
  throw new Error("Not authenticated. Please run 'vention login' to authenticate via SSO.");
6771
6779
  }
6772
6780
  const bufferMs = 5 * 60 * 1000;
@@ -6779,7 +6787,7 @@ const getAuthHeaders = async session => {
6779
6787
  session.oauth.expiresAt = refreshed.expiresInSeconds ? Date.now() + refreshed.expiresInSeconds * 1000 : session.oauth.expiresAt;
6780
6788
  await saveSession(session);
6781
6789
  }
6782
- return Object.assign(Object.assign({}, baseHeaders), {
6790
+ return Object.assign({}, baseHeaders, {
6783
6791
  Authorization: `Bearer ${session.oauth.accessToken}`
6784
6792
  });
6785
6793
  };
@@ -10113,6 +10121,11 @@ const shouldIgnoreFileSystemNode = (nodeName, ignoredNodes) => {
10113
10121
  const getSafeAppName = appName => {
10114
10122
  return appName.replace(/[^a-zA-Z0-9-_]/g, "_");
10115
10123
  };
10124
+
10125
+ /**
10126
+ * Serializes a file system directory into a JSON representation
10127
+ * Based on the logic from mm-execution-engine's MachineCodeDirectoryUtils.ts
10128
+ */
10116
10129
  const serializeFileSystem = (sourceDirectory, ignoredFileSystemNodes, shouldBase64Encoded) => {
10117
10130
  const fileSystemNode = {};
10118
10131
  const directoryContents = fs.readdirSync(sourceDirectory);
@@ -10131,11 +10144,18 @@ const serializeFileSystem = (sourceDirectory, ignoredFileSystemNodes, shouldBase
10131
10144
  });
10132
10145
  return fileSystemNode;
10133
10146
  };
10147
+
10148
+ /**
10149
+ * Creates a file system structure from a JSON representation.
10150
+ * Based on the logic from mm-execution-engine's MachineCodeDirectoryUtils.ts
10151
+ */
10134
10152
  const createFileSystemFromJson = async (fileSystemNode, targetDirectory) => {
10135
10153
  await ensureDirectoryExists(targetDirectory);
10136
10154
  const writeFiles = async (node, currentPath) => {
10137
10155
  for (const [entryName, contentOrNode] of Object.entries(node)) {
10138
10156
  const fullPath = path$2.join(currentPath, entryName);
10157
+
10158
+ // A node either represents the string contents of a file, or a subdirectory
10139
10159
  if (typeof contentOrNode === "string") {
10140
10160
  const decodedContent = Buffer.from(contentOrNode, "base64");
10141
10161
  const fileHandle = await fs.promises.open(fullPath, "w");
@@ -10152,6 +10172,9 @@ const createFileSystemFromJson = async (fileSystemNode, targetDirectory) => {
10152
10172
  }
10153
10173
  };
10154
10174
  await writeFiles(fileSystemNode, targetDirectory);
10175
+
10176
+ // fsync on directories is not supported on Windows, so we skip it there
10177
+ // On Unix systems, this ensures directory metadata is persisted to disk
10155
10178
  if (process.platform !== "win32") {
10156
10179
  const dirHandle = await fs.promises.open(targetDirectory, "r");
10157
10180
  try {
@@ -10169,15 +10192,27 @@ const ensureDirectoryExists = async directory => {
10169
10192
  });
10170
10193
  }
10171
10194
  };
10195
+
10196
+ /**
10197
+ * Checks if a directory already exists at the given path
10198
+ */
10172
10199
  const checkIfDirectoryExists = async directory => {
10173
10200
  const exists = await fs.promises.access(directory).then(() => true).catch(() => false);
10174
10201
  return exists;
10175
10202
  };
10203
+
10204
+ /**
10205
+ * Checks if an application directory with the given name already exists in the target directory
10206
+ */
10176
10207
  const checkIfAppDirectoryExists = async (appName, targetDirectory) => {
10177
10208
  const safeAppName = appName.replace(/[^a-zA-Z0-9-_]/g, "_");
10178
10209
  const appDirectoryPath = path$2.join(targetDirectory, safeAppName);
10179
10210
  return await checkIfDirectoryExists(appDirectoryPath);
10180
10211
  };
10212
+
10213
+ /**
10214
+ * Writes application source code to disk in a new directory
10215
+ */
10181
10216
  const writeApplicationToDisk = async (appName, sourceCode, targetDirectory, overwriteExisting = false) => {
10182
10217
  if (overwriteExisting) {
10183
10218
  await createFileSystemFromJson(sourceCode, targetDirectory);
@@ -10255,7 +10290,7 @@ const updateExistingAppDirectory = async (session, machineCodeAppDirectoryInfo,
10255
10290
  }
10256
10291
  const appWithSourceCode = await getAppFromDigitalTwin(session.environment, machineCodeAppDirectoryInfo, linkedAllocation.sessionId);
10257
10292
  await writeApplicationToDisk(appWithSourceCode.name, appWithSourceCode.sourceCode, currentDir, true);
10258
- await writeMachineCodeAppDirectoryInfo(currentDir, Object.assign(Object.assign({}, machineCodeAppDirectoryInfo), {
10293
+ await writeMachineCodeAppDirectoryInfo(currentDir, Object.assign({}, machineCodeAppDirectoryInfo, {
10259
10294
  lastPulledAt: new Date().toISOString()
10260
10295
  }));
10261
10296
  console.log();
@@ -10303,7 +10338,7 @@ const require$1 = createRequire(import.meta.url);
10303
10338
  function loadPackageJson() {
10304
10339
  try {
10305
10340
  return require$1("./package.json");
10306
- } catch (_a) {
10341
+ } catch (_unused) {
10307
10342
  return require$1("../package.json");
10308
10343
  }
10309
10344
  }
@@ -10475,7 +10510,7 @@ program.command("push").description("Push local changes from this directory to t
10475
10510
  };
10476
10511
  console.log(chalk.blue("Pushing to digital twin..."));
10477
10512
  await pushApplicationToDigitalTwin(session.environment, updatedMachineCodeAppDirectoryInfo.designId, linkedAllocation.sessionId, applicationToPush);
10478
- await writeMachineCodeAppDirectoryInfo(currentDir, Object.assign(Object.assign({}, updatedMachineCodeAppDirectoryInfo), {
10513
+ await writeMachineCodeAppDirectoryInfo(currentDir, Object.assign({}, updatedMachineCodeAppDirectoryInfo, {
10479
10514
  lastPushedAt: new Date().toISOString()
10480
10515
  }));
10481
10516
  console.log();
@@ -10505,9 +10540,15 @@ program.command("pull").description("Pull an application from a design to this d
10505
10540
  handleCommandError(error, "Failed to pull application");
10506
10541
  }
10507
10542
  });
10543
+
10544
+ // Only run the CLI if this is the main module (not imported for testing)
10545
+ // we need to resolve the real path, because in local development, the path is a symlink
10508
10546
  const resolvedArgv = realpathSync(process.argv[1]);
10547
+ // fileURLToPath correctly handles Windows paths (C:\...) vs Unix paths (/...)
10548
+ // Without this, Windows comparisons fail due to leading slash in pathname
10509
10549
  const resolvedUrlPath = realpathSync(fileURLToPath(import.meta.url));
10510
10550
  if (resolvedArgv === resolvedUrlPath) {
10551
+ // Only show logo when no command is provided or help is requested
10511
10552
  const showLogoConditions = [process.argv.length <= 2, process.argv.includes("--help"), process.argv.includes("-h")];
10512
10553
  if (showLogoConditions.some(Boolean)) {
10513
10554
  console.log(chalk.cyan(ventionLogo));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vention/vention-cli",
3
- "version": "0.24.2",
3
+ "version": "0.24.3",
4
4
  "description": "CLI tool for Vention applications",
5
5
  "type": "module",
6
6
  "engines": {