@xaidenlabs/uso 1.1.2 → 1.1.4

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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  The Universal Solana Orchestrator (USO) is a command-line interface designed to streamline the initialization and management of Solana development environments. By automating the installation of the Rust toolchain, Solana CLI, and Anchor framework, USO eliminates the complexity often associated with setting up a Web3 development workspace on Windows, macOS, and Linux.
4
4
 
5
- This tool is engineered to support developers at all levelsfrom beginners setting up their first environment to senior engineers managing multiple workstations.
5
+ This tool is engineered to support developers at all levels, from beginners setting up their first environment to senior engineers managing multiple workstations.
6
6
 
7
7
  ---
8
8
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xaidenlabs/uso",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "description": "Universal Solana Orchestrator - A one-command setup tool for Solana and Anchor development environments on Windows, macOS, and Linux.",
5
5
  "bin": {
6
6
  "uso": "bin/index.js"
@@ -1,8 +1,8 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
+ const os = require('os');
3
4
  const shell = require('shelljs');
4
5
  const { log, spinner } = require('../utils/logger');
5
- const { getCargoBinPath } = require('../utils/paths');
6
6
 
7
7
  const create = async (projectName) => {
8
8
  if (!projectName) {
@@ -37,6 +37,19 @@ const create = async (projectName) => {
37
37
  return;
38
38
  }
39
39
 
40
+ // 1b. Add Windows Defender exclusion for the project directory (prevents build blocks)
41
+ if (os.platform() === 'win32') {
42
+ const defSpin = spinner('Adding Windows Defender exclusion...').start();
43
+ const cargoHome = path.join(os.homedir(), '.cargo');
44
+ const cmd = `powershell -Command "Start-Process powershell -ArgumentList '-NoProfile', '-Command', 'Add-MpPreference -ExclusionPath \\\"${targetDir.replace(/\\/g, '\\\\')}\\\"; Add-MpPreference -ExclusionPath \\\"${cargoHome.replace(/\\/g, '\\\\')}\\\"' -Verb RunAs -WindowStyle Hidden -Wait"`;
45
+ const defResult = shell.exec(cmd, { silent: true });
46
+ if (defResult.code === 0) {
47
+ defSpin.succeed('Defender exclusion added (builds won\'t be blocked).');
48
+ } else {
49
+ defSpin.warn('Could not add Defender exclusion. Builds may be blocked by antivirus.');
50
+ }
51
+ }
52
+
40
53
  // 2. Customize Project (Rename files and content)
41
54
  log.info("✏️ Customizing project details...");
42
55
 
@@ -22,13 +22,26 @@ const runProxyCommand = async (command, args = []) => {
22
22
  return;
23
23
  }
24
24
 
25
- // Check for Windows privilege error (os error 1314 — symlink creation requires elevation)
26
25
  const output = (execution.stderr || '') + (execution.stdout || '');
26
+
27
+ // Detect Windows-specific errors that require elevation
27
28
  const isPrivilegeError = output.includes('os error 1314') || output.includes('A required privilege is not held by the client');
29
+ const isAppControlBlock = output.includes('os error 4551') || output.includes('Application Control policy has blocked');
28
30
 
29
- if (isPrivilegeError && os.platform() === 'win32') {
30
- log.warn("⚠️ Windows requires Administrator privileges for this operation.");
31
- log.info("🔑 Requesting elevated access...\n");
31
+ if ((isPrivilegeError || isAppControlBlock) && os.platform() === 'win32') {
32
+ if (isAppControlBlock) {
33
+ log.warn("⚠️ Windows Application Control is blocking Cargo build scripts.");
34
+ } else {
35
+ log.warn("⚠️ Windows requires Administrator privileges for this operation.");
36
+ }
37
+ log.info("🔑 Requesting elevated access (build will run in background)...\n");
38
+
39
+ // Clean stale blocked artifacts before elevated retry
40
+ if (isAppControlBlock) {
41
+ const cleanSpin = spinner('Cleaning blocked build artifacts...').start();
42
+ shell.exec('cargo clean', { silent: true, cwd: process.cwd() });
43
+ cleanSpin.succeed('Build cache cleaned.');
44
+ }
32
45
 
33
46
  await runElevatedWithProgress(command, args);
34
47
  } else {
@@ -44,34 +57,41 @@ const runProxyCommand = async (command, args = []) => {
44
57
  const runElevatedWithProgress = (command, args = []) => {
45
58
  return new Promise((resolve) => {
46
59
  const tmpDir = os.tmpdir();
47
- const logFile = path.join(tmpDir, `uso-elevated-${Date.now()}.log`);
48
- const doneFile = path.join(tmpDir, `uso-elevated-${Date.now()}.done`);
49
- const cwd = process.cwd();
60
+ const ts = Date.now();
61
+ const logFile = path.join(tmpDir, `uso-elevated-${ts}.log`).replace(/\\/g, '\\\\');
62
+ const doneFile = path.join(tmpDir, `uso-elevated-${ts}.done`).replace(/\\/g, '\\\\');
63
+ const logFileNormal = path.join(tmpDir, `uso-elevated-${ts}.log`);
64
+ const doneFileNormal = path.join(tmpDir, `uso-elevated-${ts}.done`);
65
+ const cwd = process.cwd().replace(/\\/g, '\\\\');
50
66
 
51
67
  // Write an empty log file so we can start reading immediately
52
- fs.writeFileSync(logFile, '', 'utf8');
53
-
54
- // Build the elevated command:
55
- // 1. cd into the user's project directory
56
- // 2. Run anchor <command>
57
- // 3. Write exit code to a .done file so we know it finished
58
- // 4. All stdout/stderr piped to the log file
59
- const anchorCmd = `anchor ${command} ${args.join(' ')}`;
60
- const innerScript = `
61
- Set-Location '${cwd.replace(/'/g, "''")}';
62
- $output = & cmd /c '${anchorCmd} 2>&1';
63
- $output | Out-File -FilePath '${logFile.replace(/\\/g, '\\\\')}' -Encoding utf8 -Append;
64
- $LASTEXITCODE | Out-File -FilePath '${doneFile.replace(/\\/g, '\\\\')}' -Encoding utf8;
65
- `.replace(/\n/g, ' ').trim();
66
-
67
- // Launch hidden elevated process
68
- const elevateCmd = `powershell -Command "Start-Process powershell -ArgumentList '-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', '${innerScript.replace(/'/g, "''")}' -Verb RunAs -WindowStyle Hidden"`;
69
-
70
- // This triggers the UAC prompt (that one still shows — it's Windows security, can't bypass it)
68
+ fs.writeFileSync(logFileNormal, '', 'utf8');
69
+
70
+ // Build a PowerShell script file to avoid quoting hell
71
+ const scriptFile = path.join(tmpDir, `uso-elevated-${ts}.ps1`);
72
+ const scriptContent = [
73
+ `Set-Location "${cwd}"`,
74
+ `$env:PATH = [System.Environment]::GetEnvironmentVariable("PATH", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("PATH", "User")`,
75
+ `try {`,
76
+ ` anchor ${command} ${args.join(' ')} 2>&1 | Tee-Object -FilePath "${logFile}" -Append`,
77
+ ` $LASTEXITCODE | Out-File -FilePath "${doneFile}" -Encoding utf8`,
78
+ `} catch {`,
79
+ ` $_.Exception.Message | Out-File -FilePath "${logFile}" -Encoding utf8 -Append`,
80
+ ` "1" | Out-File -FilePath "${doneFile}" -Encoding utf8`,
81
+ `}`,
82
+ ].join('\n');
83
+
84
+ fs.writeFileSync(scriptFile, scriptContent, 'utf8');
85
+
86
+ // Launch hidden elevated process using the script file
87
+ const elevateCmd = `powershell -Command "Start-Process powershell -ArgumentList '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', '${scriptFile.replace(/\\/g, '\\\\')}' -Verb RunAs -WindowStyle Hidden"`;
88
+
71
89
  const elevateResult = shell.exec(elevateCmd, { silent: true });
72
90
 
73
91
  if (elevateResult.code !== 0) {
74
92
  log.error("❌ Failed to launch elevated process. Was the UAC prompt declined?");
93
+ // Cleanup
94
+ try { fs.unlinkSync(scriptFile); } catch (e) { /* ignore */ }
75
95
  resolve();
76
96
  return;
77
97
  }
@@ -79,20 +99,20 @@ const runElevatedWithProgress = (command, args = []) => {
79
99
  // Now poll the log file and show progress
80
100
  const spin = spinner('Building with elevated privileges...').start();
81
101
  let lastSize = 0;
82
- let lastLine = '';
83
102
 
84
103
  const pollInterval = setInterval(() => {
85
104
  try {
86
105
  // Check if process finished
87
- if (fs.existsSync(doneFile)) {
106
+ if (fs.existsSync(doneFileNormal)) {
88
107
  clearInterval(pollInterval);
89
108
 
90
- const exitCode = fs.readFileSync(doneFile, 'utf8').trim();
91
- const fullLog = fs.readFileSync(logFile, 'utf8');
109
+ const exitCode = fs.readFileSync(doneFileNormal, 'utf8').trim();
110
+ const fullLog = fs.readFileSync(logFileNormal, 'utf8');
92
111
 
93
112
  // Cleanup temp files
94
- try { fs.unlinkSync(logFile); } catch (e) { /* ignore */ }
95
- try { fs.unlinkSync(doneFile); } catch (e) { /* ignore */ }
113
+ try { fs.unlinkSync(logFileNormal); } catch (e) { /* ignore */ }
114
+ try { fs.unlinkSync(doneFileNormal); } catch (e) { /* ignore */ }
115
+ try { fs.unlinkSync(scriptFile); } catch (e) { /* ignore */ }
96
116
 
97
117
  if (exitCode === '0') {
98
118
  spin.succeed(`'${command}' completed successfully (elevated).`);
@@ -109,29 +129,29 @@ const runElevatedWithProgress = (command, args = []) => {
109
129
  return;
110
130
  }
111
131
 
112
- // Read new content from log file for progress
113
- const stats = fs.statSync(logFile);
132
+ // Read new content for live progress
133
+ const stats = fs.statSync(logFileNormal);
114
134
  if (stats.size > lastSize) {
115
- const content = fs.readFileSync(logFile, 'utf8');
135
+ const content = fs.readFileSync(logFileNormal, 'utf8');
116
136
  const lines = content.trim().split('\n').filter(l => l.trim());
117
137
  if (lines.length > 0) {
118
- lastLine = lines[lines.length - 1].trim();
119
- // Show the last meaningful line as spinner text
138
+ const lastLine = lines[lines.length - 1].trim();
120
139
  const displayLine = lastLine.length > 70 ? lastLine.substring(0, 67) + '...' : lastLine;
121
140
  spin.text = displayLine;
122
141
  }
123
142
  lastSize = stats.size;
124
143
  }
125
144
  } catch (e) {
126
- // File might not exist yet or be locked, just keep polling
145
+ // File might not exist yet or be locked
127
146
  }
128
147
  }, 500);
129
148
 
130
- // Safety timeout: 10 minutes max
149
+ // Safety timeout: 10 minutes
131
150
  setTimeout(() => {
132
- if (!fs.existsSync(doneFile)) {
151
+ if (!fs.existsSync(doneFileNormal)) {
133
152
  clearInterval(pollInterval);
134
153
  spin.fail("Timed out waiting for elevated process (10 min).");
154
+ try { fs.unlinkSync(scriptFile); } catch (e) { /* ignore */ }
135
155
  resolve();
136
156
  }
137
157
  }, 10 * 60 * 1000);