@vention/vention-cli 0.21.0 → 0.22.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 +57 -16
- 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
|
|
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
|
|
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: (
|
|
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 (
|
|
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
|
|
6383
|
-
return (
|
|
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
|
|
6387
|
-
const entry = (
|
|
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 (
|
|
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
|
|
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(
|
|
6773
|
+
return Object.assign({}, baseHeaders, {
|
|
6766
6774
|
Cookie: `vention_session=${session.ventionSession}`
|
|
6767
6775
|
});
|
|
6768
6776
|
}
|
|
6769
|
-
if (!((
|
|
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(
|
|
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(
|
|
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 (
|
|
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(
|
|
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));
|