@uipath/uipath-python-bridge 0.9.1 → 1.0.2

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/dist/index.js +267 -39
  2. package/package.json +13 -7
package/dist/index.js CHANGED
@@ -88,7 +88,7 @@ function processCommandArgs(args, rules) {
88
88
  }
89
89
  return {
90
90
  allowed: true,
91
- args: [...args]
91
+ args: [...argsWithoutForce]
92
92
  };
93
93
  }
94
94
  // src/execute.ts
@@ -103,12 +103,45 @@ import {
103
103
  UIPATH_HOME_DIR
104
104
  } from "@uipath/common";
105
105
  import { getFileSystem as getFileSystem2 } from "@uipath/filesystem";
106
- async function buildAuthEnv() {
106
+ var ENV_FILE_NAME = ".env";
107
+ function isHelpCommand(args) {
108
+ return args.includes("--help") || args.includes("-h");
109
+ }
110
+ function matchesRequiredCommand(args, commands) {
111
+ return args[0] !== undefined && commands.includes(args[0]);
112
+ }
113
+ function envValueFromFile(content, name) {
114
+ const envLine = content.split(/\r?\n/).map((line) => line.trim()).find((line) => {
115
+ if (!line || line.startsWith("#")) {
116
+ return false;
117
+ }
118
+ const [key, ...valueParts2] = line.split("=");
119
+ return key.trim() === name && valueParts2.join("=").trim().length > 0;
120
+ });
121
+ if (!envLine) {
122
+ return null;
123
+ }
124
+ const [, ...valueParts] = envLine.split("=");
125
+ return valueParts.join("=").trim().replace(/^["']|["']$/g, "") || null;
126
+ }
127
+ async function getConfiguredEnvValue(fs, name) {
128
+ const processValue = process.env[name]?.trim();
129
+ if (processValue) {
130
+ return processValue;
131
+ }
132
+ const envPath = fs.path.join(fs.env.cwd(), ENV_FILE_NAME);
133
+ if (!await fs.exists(envPath)) {
134
+ return null;
135
+ }
136
+ const [readError, content] = await catchError2(fs.readFile(envPath, "utf-8"));
137
+ return !readError && typeof content === "string" ? envValueFromFile(content, name) : null;
138
+ }
139
+ async function buildAuthContext() {
107
140
  const authEnv = {};
108
141
  const [authError, status] = await catchError2(getLoginStatusAsync());
109
142
  if (authError) {
110
143
  logger2.debug(`Auth unavailable — continuing without credentials: ${authError.message}`);
111
- return authEnv;
144
+ return { authEnv };
112
145
  }
113
146
  if (status.loginStatus === "Logged in" && status.accessToken) {
114
147
  authEnv.UIPATH_ACCESS_TOKEN = status.accessToken;
@@ -128,10 +161,16 @@ async function buildAuthEnv() {
128
161
  if (status.tenantName)
129
162
  authEnv.UIPATH_TENANT_NAME = status.tenantName;
130
163
  }
131
- return authEnv;
164
+ return { authEnv, loginStatus: status };
132
165
  }
133
166
  function createExecuteCommand(config) {
134
- const { commandPrefix, telemetryEvent, commandRules } = config;
167
+ const {
168
+ commandPrefix,
169
+ telemetryEvent,
170
+ commandRules,
171
+ commandPreflight,
172
+ requiredEnvironmentVariables = []
173
+ } = config;
135
174
  const executeCommand = async (rawArgs, _options) => {
136
175
  const runIndex = rawArgs.indexOf("exec");
137
176
  const actualArgs = runIndex >= 0 ? rawArgs.slice(runIndex + 1) : [];
@@ -139,9 +178,9 @@ function createExecuteCommand(config) {
139
178
  const cacheFile = fs.path.join(fs.env.homedir(), UIPATH_HOME_DIR, getCacheFileName());
140
179
  const setupHint = commandPrefix ? `Run 'uip ${commandPrefix} setup' first to configure environment.` : "Run setup first to configure environment.";
141
180
  const cache = await readCache(cacheFile);
142
- if (!cache || !cache.uipathExePath) {
181
+ if (!cache?.uipathExePath) {
143
182
  OutputFormatter.error({
144
- Result: RESULTS.Failure,
183
+ Result: RESULTS.ConfigError,
145
184
  Message: "Python not configured.",
146
185
  Instructions: setupHint
147
186
  });
@@ -150,7 +189,7 @@ function createExecuteCommand(config) {
150
189
  }
151
190
  if (!await fs.exists(cache.uipathExePath)) {
152
191
  OutputFormatter.error({
153
- Result: RESULTS.Failure,
192
+ Result: RESULTS.ConfigError,
154
193
  Message: "uipath executable not found.",
155
194
  Instructions: setupHint
156
195
  });
@@ -160,20 +199,64 @@ function createExecuteCommand(config) {
160
199
  const processed = processCommandArgs(actualArgs, commandRules);
161
200
  if (!processed.allowed) {
162
201
  OutputFormatter.error({
163
- Result: RESULTS.Failure,
202
+ Result: RESULTS.ValidationError,
164
203
  Message: processed.errorMessage || "Command disabled.",
165
204
  Instructions: "Use of --force flag is not recommended. Favor alternate command."
166
205
  });
167
- processContext.exit(1);
206
+ processContext.exit(3);
168
207
  return;
169
208
  }
170
209
  const commandArgs = commandPrefix ? [commandPrefix, ...processed.args] : [...processed.args];
171
- const authEnv = await buildAuthEnv();
210
+ const requiredEnv = {};
211
+ for (const requirement of requiredEnvironmentVariables) {
212
+ const applies = matchesRequiredCommand(processed.args, requirement.commands) && (!requirement.skipWhenHelp || !isHelpCommand(processed.args));
213
+ if (!applies) {
214
+ continue;
215
+ }
216
+ const value = await getConfiguredEnvValue(fs, requirement.name);
217
+ if (!value) {
218
+ OutputFormatter.error({
219
+ Result: RESULTS.ConfigError,
220
+ Message: `${requirement.name} environment variable not found.`,
221
+ Instructions: requirement.instructions
222
+ });
223
+ processContext.exit(1);
224
+ return;
225
+ }
226
+ requiredEnv[requirement.name] = value;
227
+ }
228
+ const { authEnv, loginStatus } = await buildAuthContext();
229
+ if (commandPreflight) {
230
+ const [preflightError, preflightResult] = await catchError2(Promise.resolve().then(() => commandPreflight({
231
+ args: commandArgs,
232
+ loginStatus,
233
+ authEnv
234
+ })));
235
+ if (preflightError) {
236
+ OutputFormatter.error({
237
+ Result: RESULTS.Failure,
238
+ Message: preflightError.message,
239
+ Instructions: "Check your credentials and configuration, then retry."
240
+ });
241
+ processContext.exit(1);
242
+ return;
243
+ }
244
+ if (preflightResult && !preflightResult.allowed) {
245
+ OutputFormatter.error({
246
+ Result: RESULTS.ConfigError,
247
+ Message: preflightResult.message || "Command preflight check failed.",
248
+ Instructions: preflightResult.instructions || "Update the command configuration and try again."
249
+ });
250
+ processContext.exit(preflightResult.exitCode ?? 1);
251
+ return;
252
+ }
253
+ }
172
254
  const proc = spawn(cache.uipathExePath, commandArgs, {
173
255
  cwd: fs.env.cwd(),
174
256
  env: {
175
257
  ...process.env,
176
- ...authEnv
258
+ ...authEnv,
259
+ ...requiredEnv
177
260
  },
178
261
  stdio: "inherit"
179
262
  });
@@ -209,6 +292,7 @@ class PythonService {
209
292
  cacheFile;
210
293
  config;
211
294
  uipathExePath;
295
+ packageVersion;
212
296
  constructor(config) {
213
297
  const fs = getFileSystem3();
214
298
  this.config = config;
@@ -228,23 +312,39 @@ class PythonService {
228
312
  if (!force) {
229
313
  logger3.debug("Checking cache...");
230
314
  const cached = await this.loadCache();
231
- if (cached && cached.uipathExePath && await fs.exists(cached.uipathExePath)) {
315
+ if (cached?.uipathExePath && await fs.exists(cached.uipathExePath)) {
232
316
  pythonPath = cached.pythonPath;
233
317
  version = cached.version;
234
318
  this.uipathExePath = cached.uipathExePath;
235
319
  logger3.info(`Using cached uipath executable: ${this.uipathExePath}`);
320
+ this.packageVersion = cached.packageVersion;
321
+ if (!this.packageVersion) {
322
+ const versionResult2 = await this.getUipathVersion(this.uipathExePath);
323
+ if ("error" in versionResult2) {
324
+ return {
325
+ success: false,
326
+ pythonPath,
327
+ pythonVersion: version,
328
+ packageInstalled: true,
329
+ error: versionResult2.error
330
+ };
331
+ }
332
+ this.packageVersion = versionResult2.version;
333
+ }
236
334
  await this.saveCache({
237
335
  pythonPath,
238
336
  version,
239
337
  lastValidated: new Date().toISOString(),
240
338
  packageName,
241
- uipathExePath: this.uipathExePath
339
+ uipathExePath: this.uipathExePath,
340
+ packageVersion: this.packageVersion
242
341
  });
243
342
  return {
244
343
  success: true,
245
344
  pythonPath,
246
345
  pythonVersion: version,
247
- packageInstalled: true
346
+ packageInstalled: true,
347
+ packageVersion: this.packageVersion
248
348
  };
249
349
  } else if (cached) {
250
350
  logger3.warn("Cached Python path no longer exists, re-detecting...");
@@ -273,18 +373,31 @@ To install the package, run:
273
373
  ${pythonPath} -m pip install ${packageName}`
274
374
  };
275
375
  }
376
+ const versionResult = await this.getUipathVersion(this.uipathExePath);
377
+ if ("error" in versionResult) {
378
+ return {
379
+ success: false,
380
+ pythonPath,
381
+ pythonVersion: version,
382
+ packageInstalled: true,
383
+ error: versionResult.error
384
+ };
385
+ }
386
+ this.packageVersion = versionResult.version;
276
387
  await this.saveCache({
277
388
  pythonPath,
278
389
  version,
279
390
  lastValidated: new Date().toISOString(),
280
391
  packageName,
281
- uipathExePath: this.uipathExePath
392
+ uipathExePath: this.uipathExePath,
393
+ packageVersion: this.packageVersion
282
394
  });
283
395
  return {
284
396
  success: true,
285
397
  pythonPath,
286
398
  pythonVersion: version,
287
- packageInstalled: true
399
+ packageInstalled: true,
400
+ packageVersion: this.packageVersion
288
401
  };
289
402
  }
290
403
  async detectPython() {
@@ -349,26 +462,17 @@ To install the package, run:
349
462
  };
350
463
  }
351
464
  async findUipathExe(fs, command, args) {
465
+ const metadataPath = await this.findUipathExeFromMetadata(fs, command, args);
466
+ if (metadataPath)
467
+ return metadataPath;
352
468
  const isWindows = platform() === "win32";
353
469
  const candidatePaths = [];
354
470
  const virtualEnv = process.env.VIRTUAL_ENV;
355
471
  if (virtualEnv) {
356
472
  candidatePaths.push(isWindows ? fs.path.join(virtualEnv, "Scripts", "uipath.exe") : fs.path.join(virtualEnv, "bin", "uipath"));
357
473
  }
358
- const prefixArgs = [...args, "-c", "import sys; print(sys.prefix)"];
359
- const prefixProc = spawn2(command, prefixArgs, {
360
- stdio: ["ignore", "pipe", "pipe"]
361
- });
362
- const prefixChunks = [];
363
- prefixProc.stdout.on("data", (chunk) => prefixChunks.push(chunk));
364
- const [prefixError, prefixExitCode] = await catchError3(new Promise((resolve, reject) => {
365
- prefixProc.on("exit", (code) => resolve(code ?? 1));
366
- prefixProc.on("error", (error) => reject(error));
367
- }));
368
- if (prefixError) {
369
- logger3.debug(`Failed to locate uipath executable: ${prefixError}`);
370
- } else if (prefixExitCode === 0) {
371
- const prefix = Buffer.concat(prefixChunks).toString("utf-8").trim();
474
+ const prefix = await this.getPythonPrefix(command, args);
475
+ if (prefix) {
372
476
  candidatePaths.push(isWindows ? fs.path.join(prefix, "Scripts", "uipath.exe") : fs.path.join(prefix, "bin", "uipath"));
373
477
  }
374
478
  for (const candidate of candidatePaths) {
@@ -378,9 +482,132 @@ To install the package, run:
378
482
  return candidate;
379
483
  }
380
484
  }
381
- logger3.error(`uipath executable not found. Checked: ${candidatePaths.join(", ")}`);
485
+ const pathCandidates = this.getPathUipathCandidates(fs, isWindows);
486
+ for (const candidate of pathCandidates) {
487
+ logger3.debug(`Checking for uipath executable on PATH at: ${candidate}`);
488
+ if (await fs.exists(candidate)) {
489
+ logger3.info(`Found uipath executable at: ${candidate}`);
490
+ return candidate;
491
+ }
492
+ }
493
+ const checkedCandidates = [...candidatePaths, ...pathCandidates];
494
+ logger3.error(`uipath executable not found. Checked: ${checkedCandidates.join(", ")}`);
382
495
  return;
383
496
  }
497
+ async findUipathExeFromMetadata(fs, command, args) {
498
+ const script = `
499
+ from importlib.metadata import distribution, PackageNotFoundError
500
+ import sys
501
+ try:
502
+ d = distribution("uipath")
503
+ except PackageNotFoundError:
504
+ sys.exit(2)
505
+ for f in d.files or []:
506
+ if f.name.lower() in ("uipath", "uipath.exe"):
507
+ print(str(f.locate()))
508
+ sys.exit(0)
509
+ sys.exit(3)
510
+ `;
511
+ const proc = spawn2(command, [...args, "-c", script], {
512
+ stdio: ["ignore", "pipe", "pipe"]
513
+ });
514
+ const stdoutChunks = [];
515
+ const stderrChunks = [];
516
+ proc.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
517
+ proc.stderr.on("data", (chunk) => stderrChunks.push(chunk));
518
+ const [error, exitCode] = await catchError3(new Promise((resolve, reject) => {
519
+ proc.on("exit", (code) => resolve(code ?? 1));
520
+ proc.on("error", (err) => reject(err));
521
+ }));
522
+ if (error) {
523
+ logger3.debug(`Failed to locate uipath executable: ${error}`);
524
+ return;
525
+ }
526
+ if (exitCode !== 0) {
527
+ const stderr = Buffer.concat(stderrChunks).toString("utf-8").trim();
528
+ logger3.debug(`uipath executable metadata lookup exited with code ${exitCode}${stderr ? `: ${stderr}` : ""}`);
529
+ return;
530
+ }
531
+ const resolved = Buffer.concat(stdoutChunks).toString("utf-8").trim();
532
+ if (!resolved) {
533
+ logger3.debug("uipath executable metadata lookup returned empty path");
534
+ return;
535
+ }
536
+ if (!await fs.exists(resolved)) {
537
+ logger3.debug(`uipath executable path from metadata does not exist: ${resolved}`);
538
+ return;
539
+ }
540
+ logger3.info(`Found uipath executable at: ${resolved}`);
541
+ return resolved;
542
+ }
543
+ async getPythonPrefix(command, args) {
544
+ const proc = spawn2(command, [...args, "-c", "import sys; print(sys.prefix)"], {
545
+ stdio: ["ignore", "pipe", "pipe"]
546
+ });
547
+ const stdoutChunks = [];
548
+ proc.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
549
+ const [error, exitCode] = await catchError3(new Promise((resolve, reject) => {
550
+ proc.on("exit", (code) => resolve(code ?? 1));
551
+ proc.on("error", (err) => reject(err));
552
+ }));
553
+ if (error) {
554
+ logger3.debug(`Failed to locate uipath executable: ${error}`);
555
+ return;
556
+ }
557
+ if (exitCode !== 0)
558
+ return;
559
+ const prefix = Buffer.concat(stdoutChunks).toString("utf-8").trim();
560
+ return prefix || undefined;
561
+ }
562
+ async getUipathVersion(exePath) {
563
+ const proc = spawn2(exePath, ["--version"], {
564
+ stdio: ["ignore", "pipe", "pipe"]
565
+ });
566
+ const stdoutChunks = [];
567
+ const stderrChunks = [];
568
+ proc.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
569
+ proc.stderr.on("data", (chunk) => stderrChunks.push(chunk));
570
+ const [spawnError, exitCode] = await catchError3(new Promise((resolve, reject) => {
571
+ proc.on("exit", (code) => resolve(code ?? 1));
572
+ proc.on("error", (err) => reject(err));
573
+ }));
574
+ if (spawnError) {
575
+ return {
576
+ error: `Failed to run '${exePath} --version': ${spawnError.message}`
577
+ };
578
+ }
579
+ const stdout = Buffer.concat(stdoutChunks).toString("utf-8").trim();
580
+ const stderr = Buffer.concat(stderrChunks).toString("utf-8").trim();
581
+ if (exitCode !== 0) {
582
+ return {
583
+ error: `'${exePath} --version' exited with code ${exitCode}${stderr ? `: ${stderr}` : ""}`
584
+ };
585
+ }
586
+ const match = stdout.match(/uipath\s+version\s+(\S+)/i);
587
+ if (!match) {
588
+ return {
589
+ error: `Could not parse version from '${exePath} --version' output: ${stdout || "<empty>"}`
590
+ };
591
+ }
592
+ return { version: match[1] };
593
+ }
594
+ getPathUipathCandidates(fs, isWindows) {
595
+ const pathValue = process.env.PATH || (isWindows ? process.env.Path : undefined);
596
+ if (!pathValue)
597
+ return [];
598
+ const delimiter = isWindows ? ";" : ":";
599
+ const pathEntries = pathValue.split(delimiter).map((entry) => entry.trim().replace(/^"|"$/g, "")).filter((entry) => entry.length > 0);
600
+ const executableNames = isWindows ? this.getWindowsExecutableNames() : ["uipath"];
601
+ return pathEntries.flatMap((entry) => executableNames.map((name) => fs.path.join(entry, name)));
602
+ }
603
+ getWindowsExecutableNames() {
604
+ const pathExt = process.env.PATHEXT || ".COM;.EXE;.BAT;.CMD";
605
+ const extensions = pathExt.split(";").map((extension) => extension.trim().toLowerCase()).filter((extension) => extension.length > 0).map((extension) => extension.startsWith(".") ? extension : `.${extension}`);
606
+ return [
607
+ "uipath",
608
+ ...extensions.map((extension) => `uipath${extension}`)
609
+ ];
610
+ }
384
611
  buildNotFoundError(checkedVersions, searchedCommands) {
385
612
  let msg = `No compatible Python installation found.
386
613
 
@@ -449,7 +676,7 @@ Please install one of the required Python versions:
449
676
  async saveCache(cache) {
450
677
  const fs = getFileSystem3();
451
678
  const dir = fs.path.dirname(this.cacheFile);
452
- const [mkdirError] = await catchError3(fs.mkdir(dir, { recursive: true }));
679
+ const [mkdirError] = await catchError3(fs.mkdir(dir));
453
680
  if (mkdirError) {
454
681
  logger3.warn(`Failed to create cache directory: ${mkdirError}`);
455
682
  return;
@@ -472,7 +699,7 @@ import {
472
699
  import { getFileSystem as getFileSystem4 } from "@uipath/filesystem";
473
700
  function createSetupCommand(config) {
474
701
  return (program) => {
475
- program.command("setup").description("Detect Python installation and verify package is installed").option("--force", "Force re-detection of Python even if cached", false).trackedAction(processContext2, config.telemetryEvent, async (options) => {
702
+ program.command("setup").description("Detect Python installation and verify package is installed").option("--force", "Force re-detection of Python even if cached", false).trackedAction(processContext2, async (options) => {
476
703
  const packageName = config.packageName || getPackageName();
477
704
  const allowedVersions = getAllowedPythonVersions();
478
705
  if (allowedVersions.length === 0) {
@@ -506,7 +733,7 @@ Searching for Python installations: ${allowedVersions}`);
506
733
  OutputFormatter2.error({
507
734
  Result: RESULTS2.Failure,
508
735
  Message: result.error || "Setup failed with no error.",
509
- Instructions: result.instructions || ""
736
+ Instructions: result.instructions || `Install a supported Python version and the '${packageName}' tool, then re-run setup. For uv installs, run 'uv tool install ${packageName}' and ensure the uv tool directory is on PATH.`
510
737
  });
511
738
  processContext2.exit(1);
512
739
  return;
@@ -521,21 +748,22 @@ Searching for Python installations: ${allowedVersions}`);
521
748
  PackageVersion: result.packageVersion ?? "N/A"
522
749
  }
523
750
  });
751
+ processContext2.exit(0);
524
752
  });
525
753
  };
526
754
  }
527
- function createDefaultAction(config, executeCommand) {
755
+ function createDefaultAction(_config, executeCommand) {
528
756
  return (program) => {
529
- program.trackedAction(processContext2, config.defaultTelemetryEvent, async () => {
757
+ program.trackedAction(processContext2, async () => {
530
758
  const userArgs = program.args.length > 0 ? program.args : [];
531
759
  const extra = userArgs.length > 0 ? userArgs : ["--help"];
532
760
  await executeCommand(["_", "_", "exec", ...extra], {});
533
761
  });
534
762
  };
535
763
  }
536
- function createHelpCommand(config, executeCommand) {
764
+ function createHelpCommand(_config, executeCommand) {
537
765
  return (program) => {
538
- program.command("help").description("Display help for the python tool").trackedAction(processContext2, config.helpTelemetryEvent, async () => {
766
+ program.command("help").description("Display help for the python tool").trackedAction(processContext2, async () => {
539
767
  await executeCommand(["_", "_", "exec", "--help"], {});
540
768
  });
541
769
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uipath/uipath-python-bridge",
3
- "version": "0.9.1",
3
+ "version": "1.0.2",
4
4
  "description": "Shared Python detection, caching, and CLI bridge for UiPath Python SDK tools.",
5
5
  "keywords": [
6
6
  "uip",
@@ -30,13 +30,19 @@
30
30
  "lint": "biome check .",
31
31
  "lint:fix": "biome check --write ."
32
32
  },
33
+ "peerDependencies": {
34
+ "@uipath/auth": "^1.0.0",
35
+ "@uipath/common": "^1.0.0",
36
+ "@uipath/filesystem": "^1.0.0",
37
+ "commander": "^14.0.3"
38
+ },
33
39
  "devDependencies": {
34
- "@uipath/auth": "0.9.1",
35
- "@uipath/common": "0.9.1",
36
- "@uipath/filesystem": "0.9.1",
37
- "@types/bun": "^1.3.9",
40
+ "@uipath/auth": "1.0.2",
41
+ "@uipath/common": "1.0.2",
42
+ "@uipath/filesystem": "1.0.2",
43
+ "@types/bun": "^1.3.11",
38
44
  "commander": "^14.0.3",
39
- "typescript": "^5"
45
+ "typescript": "^6.0.2"
40
46
  },
41
- "gitHead": "e8da2857e37a9495c4907cd39f47c9d6ed1a5566"
47
+ "gitHead": "e373bfc1657e0bd85a7dc482691f4bda590392e5"
42
48
  }