@xaidenlabs/uso 1.1.67 → 1.1.69
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/package.json +36 -36
- package/src/commands/doctor.js +202 -126
- package/src/commands/init.js +4 -3
- package/src/commands/uninstall.js +374 -199
- package/src/commands/workflow.js +443 -356
- package/src/platforms/linux.js +1 -1
- package/src/platforms/wsl.js +329 -203
package/src/commands/workflow.js
CHANGED
|
@@ -1,70 +1,129 @@
|
|
|
1
|
-
const shell = require(
|
|
2
|
-
const os = require(
|
|
3
|
-
const fs = require(
|
|
4
|
-
const path = require(
|
|
5
|
-
const { log, spinner } = require(
|
|
6
|
-
const { isStealthMode } = require(
|
|
7
|
-
const { runWsl, toWslPath } = require(
|
|
8
|
-
|
|
9
|
-
const
|
|
1
|
+
const shell = require("shelljs");
|
|
2
|
+
const os = require("os");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { log, spinner } = require("../utils/logger");
|
|
6
|
+
const { isStealthMode } = require("../utils/stealth");
|
|
7
|
+
const { runWsl, toWslPath } = require("../utils/wsl-bridge");
|
|
8
|
+
|
|
9
|
+
const isUsoCoreBridgeEnabled = () =>
|
|
10
|
+
process.env.USE_INTENT_SDK === "1" || process.env.USO_CORE_BRIDGE === "1";
|
|
11
|
+
|
|
12
|
+
const runViaUsoCoreBridge = async (command, args = [], binary = "anchor") => {
|
|
13
|
+
try {
|
|
14
|
+
const {
|
|
15
|
+
runCliIntentAdapter,
|
|
16
|
+
} = require("../../packages/uso-core/dist/cjs/adapters/cli.js");
|
|
10
17
|
const stealth = isStealthMode();
|
|
11
18
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
const result = await runCliIntentAdapter({
|
|
20
|
+
command,
|
|
21
|
+
args,
|
|
22
|
+
binary,
|
|
23
|
+
cwd: process.cwd(),
|
|
24
|
+
preferWsl: !!stealth.enabled,
|
|
25
|
+
});
|
|
18
26
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const execution = runWsl(`${envSetup} && ${fullCommand}`, { distro: stealth.distro, cwd: process.cwd() });
|
|
27
|
+
if (!result.handled) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
23
30
|
|
|
24
|
-
|
|
31
|
+
if (result.ok) {
|
|
32
|
+
return true;
|
|
25
33
|
}
|
|
26
34
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return;
|
|
35
|
+
log.warn(
|
|
36
|
+
`⚠️ uso-core bridge returned status '${result.status || "failed"}'. Falling back to legacy workflow.`,
|
|
37
|
+
);
|
|
38
|
+
if (result.reason) {
|
|
39
|
+
log.warn(`uso-core reason: ${result.reason}`);
|
|
33
40
|
}
|
|
41
|
+
return false;
|
|
42
|
+
} catch (err) {
|
|
43
|
+
log.warn(
|
|
44
|
+
"⚠️ uso-core bridge unavailable. Falling back to legacy workflow.",
|
|
45
|
+
);
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
34
49
|
|
|
35
|
-
|
|
50
|
+
const runProxyCommand = async (command, args = [], binary = "anchor") => {
|
|
51
|
+
if (isUsoCoreBridgeEnabled()) {
|
|
52
|
+
const bridged = await runViaUsoCoreBridge(command, args, binary);
|
|
53
|
+
if (bridged) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const stealth = isStealthMode();
|
|
59
|
+
|
|
60
|
+
// --- STEALTH WSL MODE ---
|
|
61
|
+
// Smart routing: prefer native binary if available (e.g. native Anchor on Windows
|
|
62
|
+
// connecting to WSL validator via localhost). Only WSL-route if native binary is missing.
|
|
63
|
+
const nativeAvailable = shell.which(binary);
|
|
64
|
+
if (stealth.enabled && !nativeAvailable) {
|
|
65
|
+
const fullCommand = `${binary} ${command} ${args.join(" ")}`;
|
|
66
|
+
|
|
67
|
+
// Source cargo/solana paths inside WSL before running
|
|
68
|
+
const envSetup =
|
|
69
|
+
'source $HOME/.cargo/env 2>/dev/null; export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"';
|
|
70
|
+
const wslCwd = toWslPath(process.cwd());
|
|
71
|
+
const execution = runWsl(`${envSetup} && ${fullCommand}`, {
|
|
72
|
+
distro: stealth.distro,
|
|
73
|
+
cwd: process.cwd(),
|
|
74
|
+
});
|
|
36
75
|
|
|
37
|
-
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
38
78
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
79
|
+
// --- NATIVE MODE ---
|
|
80
|
+
// Check if binary is available
|
|
81
|
+
if (!shell.which(binary)) {
|
|
82
|
+
log.error(`❌ ${binary} is not found in PATH.`);
|
|
83
|
+
log.warn(
|
|
84
|
+
"👉 Run 'uso init' (or 'uso install') to set up your environment.",
|
|
85
|
+
);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
42
88
|
|
|
43
|
-
|
|
89
|
+
const fullCommand = `${binary} ${command} ${args.join(" ")}`;
|
|
44
90
|
|
|
45
|
-
|
|
46
|
-
const isPrivilegeError = output.includes('os error 1314') || output.includes('A required privilege is not held by the client');
|
|
47
|
-
const isAppControlBlock = output.includes('os error 4551') || output.includes('Application Control policy has blocked');
|
|
91
|
+
const execution = shell.exec(fullCommand);
|
|
48
92
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
} else {
|
|
53
|
-
log.warn("⚠️ Windows requires Administrator privileges for this operation.");
|
|
54
|
-
}
|
|
93
|
+
if (execution.code === 0) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
55
96
|
|
|
97
|
+
const output = (execution.stderr || "") + (execution.stdout || "");
|
|
56
98
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
99
|
+
// Detect Windows-specific errors that require elevation
|
|
100
|
+
const isPrivilegeError =
|
|
101
|
+
output.includes("os error 1314") ||
|
|
102
|
+
output.includes("A required privilege is not held by the client");
|
|
103
|
+
const isAppControlBlock =
|
|
104
|
+
output.includes("os error 4551") ||
|
|
105
|
+
output.includes("Application Control policy has blocked");
|
|
63
106
|
|
|
64
|
-
|
|
107
|
+
if ((isPrivilegeError || isAppControlBlock) && os.platform() === "win32") {
|
|
108
|
+
if (isAppControlBlock) {
|
|
109
|
+
log.warn("⚠️ Windows Application Control is blocking build scripts.");
|
|
65
110
|
} else {
|
|
66
|
-
|
|
111
|
+
log.warn(
|
|
112
|
+
"⚠️ Windows requires Administrator privileges for this operation.",
|
|
113
|
+
);
|
|
67
114
|
}
|
|
115
|
+
|
|
116
|
+
// Clean stale blocked artifacts before elevated retry (only for cargo/anchor builds)
|
|
117
|
+
if (isAppControlBlock && binary === "anchor") {
|
|
118
|
+
const cleanSpin = spinner("Cleaning blocked build artifacts...").start();
|
|
119
|
+
shell.exec("cargo clean", { silent: true, cwd: process.cwd() });
|
|
120
|
+
cleanSpin.succeed("Build cache cleaned.");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
await runElevatedWithProgress(command, args, binary);
|
|
124
|
+
} else {
|
|
125
|
+
// Command failed (non-elevated)
|
|
126
|
+
}
|
|
68
127
|
};
|
|
69
128
|
|
|
70
129
|
/**
|
|
@@ -72,20 +131,22 @@ const runProxyCommand = async (command, args = [], binary = 'anchor') => {
|
|
|
72
131
|
* This is the robust fallback for Smart App Control / WDAC errors.
|
|
73
132
|
* We rely on the user to see the output in the new window.
|
|
74
133
|
*/
|
|
75
|
-
const runElevatedWithProgress = (command, args = [], binary =
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
134
|
+
const runElevatedWithProgress = (command, args = [], binary = "anchor") => {
|
|
135
|
+
return new Promise((resolve, reject) => {
|
|
136
|
+
const cwd = process.cwd().replace(/\\/g, "\\\\");
|
|
137
|
+
const cargoBin = path
|
|
138
|
+
.join(os.homedir(), ".cargo", "bin")
|
|
139
|
+
.replace(/\\/g, "\\\\");
|
|
140
|
+
const progressFile = path.join(process.cwd(), "uso-elevation.log");
|
|
141
|
+
const progressFileEscaped = progressFile.replace(/\\/g, "\\\\");
|
|
142
|
+
|
|
143
|
+
// 1. Prepare progress file
|
|
144
|
+
try {
|
|
145
|
+
fs.writeFileSync(progressFile, "");
|
|
146
|
+
} catch (e) {}
|
|
147
|
+
|
|
148
|
+
// 2. Construct Elevated Command
|
|
149
|
+
const innerCmd = `
|
|
89
150
|
$ErrorActionPreference = "Stop";
|
|
90
151
|
Start-Transcript -Path "${progressFileEscaped}" -Append -Force | Out-Null;
|
|
91
152
|
|
|
@@ -104,7 +165,7 @@ const runElevatedWithProgress = (command, args = [], binary = 'anchor') => {
|
|
|
104
165
|
|
|
105
166
|
try {
|
|
106
167
|
# Run command and let Transcript capture output
|
|
107
|
-
& ${binary} ${command} ${args.join(
|
|
168
|
+
& ${binary} ${command} ${args.join(" ")};
|
|
108
169
|
|
|
109
170
|
if ($LASTEXITCODE -eq 0) {
|
|
110
171
|
Write-Host "✅ Success!" -ForegroundColor Green;
|
|
@@ -121,207 +182,229 @@ const runElevatedWithProgress = (command, args = [], binary = 'anchor') => {
|
|
|
121
182
|
Stop-Transcript | Out-Null;
|
|
122
183
|
Write-Host "Press any key to close..." -ForegroundColor Gray;
|
|
123
184
|
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown");
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
185
|
+
`
|
|
186
|
+
.replace(/\n/g, " ")
|
|
187
|
+
.replace(/\s+/g, " ")
|
|
188
|
+
.trim();
|
|
189
|
+
|
|
190
|
+
// 3. Spawn Window (-NoExit to prevent immediate close on crash)
|
|
191
|
+
const innerCmdBytes = Buffer.from(innerCmd, "utf16le");
|
|
192
|
+
const innerCmdBase64 = innerCmdBytes.toString("base64");
|
|
193
|
+
const psCmd = `powershell -Command "Start-Process powershell -ArgumentList '-NoExit', '-EncodedCommand', '${innerCmdBase64}' -Verb RunAs"`;
|
|
194
|
+
shell.exec(psCmd);
|
|
195
|
+
|
|
196
|
+
// 4. Stream Log to Main Console
|
|
197
|
+
let lastSize = 0;
|
|
198
|
+
const spin = spinner(
|
|
199
|
+
"Waiting for output from elevated terminal...",
|
|
200
|
+
).start();
|
|
201
|
+
|
|
202
|
+
const checkInterval = setInterval(() => {
|
|
203
|
+
try {
|
|
204
|
+
if (!fs.existsSync(progressFile)) return;
|
|
205
|
+
|
|
206
|
+
const stats = fs.statSync(progressFile);
|
|
207
|
+
if (stats.size > lastSize) {
|
|
208
|
+
const fd = fs.openSync(progressFile, "r");
|
|
209
|
+
const buffer = Buffer.alloc(stats.size - lastSize);
|
|
210
|
+
fs.readSync(fd, buffer, 0, buffer.length, lastSize);
|
|
211
|
+
fs.closeSync(fd);
|
|
212
|
+
|
|
213
|
+
const rawText = buffer.toString("utf8");
|
|
214
|
+
const displayText = rawText
|
|
215
|
+
.replace(/USO_AC_SUCCESS/g, "")
|
|
216
|
+
.replace(/USO_AC_FAILURE/g, "");
|
|
217
|
+
|
|
218
|
+
// Stop spinner to print logs cleanly
|
|
219
|
+
spin.stop();
|
|
220
|
+
process.stdout.write(displayText);
|
|
221
|
+
|
|
222
|
+
lastSize = stats.size;
|
|
223
|
+
|
|
224
|
+
if (rawText.includes("USO_AC_SUCCESS")) {
|
|
225
|
+
clearInterval(checkInterval);
|
|
226
|
+
spin.succeed("Elevated process completed successfully.");
|
|
227
|
+
resolve();
|
|
228
|
+
} else if (rawText.includes("USO_AC_FAILURE")) {
|
|
229
|
+
clearInterval(checkInterval);
|
|
230
|
+
spin.fail("Elevated process reported failure.");
|
|
231
|
+
resolve();
|
|
232
|
+
} else {
|
|
233
|
+
// Restart spinner with "Building..." status since we are receiving data
|
|
234
|
+
spin.start("Building...");
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
} catch (e) {
|
|
238
|
+
// Ignore busy/lock errors during polling
|
|
239
|
+
}
|
|
240
|
+
}, 500);
|
|
241
|
+
});
|
|
176
242
|
};
|
|
177
243
|
|
|
178
|
-
const build = () => runProxyCommand(
|
|
179
|
-
const deploy = () => runProxyCommand(
|
|
180
|
-
const clean = () => runProxyCommand(
|
|
244
|
+
const build = () => runProxyCommand("build", [], "anchor");
|
|
245
|
+
const deploy = () => runProxyCommand("deploy", [], "anchor");
|
|
246
|
+
const clean = () => runProxyCommand("clean", [], "anchor");
|
|
181
247
|
|
|
182
248
|
const test = async (args = []) => {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
249
|
+
const userArgs = Array.isArray(args) ? args : [];
|
|
250
|
+
|
|
251
|
+
// On Windows, auto-detect validator to prevent anchor test from hanging
|
|
252
|
+
if (
|
|
253
|
+
os.platform() === "win32" &&
|
|
254
|
+
!userArgs.includes("--skip-local-validator")
|
|
255
|
+
) {
|
|
256
|
+
const isValidatorUp = () => {
|
|
257
|
+
const res = shell.exec("netstat -an | findstr 8899", { silent: true });
|
|
258
|
+
return res.code === 0 && res.stdout.length > 0;
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
if (isValidatorUp()) {
|
|
262
|
+
log.info("✅ Validator detected on port 8899. Running tests against it.");
|
|
263
|
+
userArgs.push("--skip-local-validator");
|
|
264
|
+
} else {
|
|
265
|
+
log.warn("⚠️ No validator detected. Starting one first...");
|
|
266
|
+
log.info(
|
|
267
|
+
"👉 Run 'uso val' in a separate terminal, then re-run 'uso test'.",
|
|
268
|
+
);
|
|
269
|
+
log.info(" Or use 'uso dev' to do both automatically.");
|
|
270
|
+
return;
|
|
201
271
|
}
|
|
272
|
+
}
|
|
202
273
|
|
|
203
|
-
|
|
274
|
+
return runProxyCommand("test", userArgs, "anchor");
|
|
204
275
|
};
|
|
205
|
-
const address = () => runProxyCommand(
|
|
276
|
+
const address = () => runProxyCommand("address", [], "solana");
|
|
206
277
|
const balance = (addrArg) => {
|
|
207
|
-
|
|
208
|
-
|
|
278
|
+
const args = addrArg ? [addrArg] : [];
|
|
279
|
+
return runProxyCommand("balance", args, "solana");
|
|
209
280
|
};
|
|
210
281
|
const airdrop = (amount, recipient) => {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
282
|
+
const args = [amount];
|
|
283
|
+
if (recipient) args.push(recipient);
|
|
284
|
+
return runProxyCommand("airdrop", args, "solana");
|
|
214
285
|
};
|
|
215
286
|
|
|
216
287
|
const validator = async (args = []) => {
|
|
217
|
-
|
|
288
|
+
const stealth = isStealthMode();
|
|
218
289
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
// Unix sockets (admin.rpc) don't work on NTFS-mounted paths (/mnt/c/).
|
|
224
|
-
// Force ledger onto native Linux filesystem.
|
|
225
|
-
if (!flags.some(f => f.startsWith('--ledger'))) {
|
|
226
|
-
flags.push('--ledger', '/tmp/test-ledger');
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const cmdArgs = flags.join(' ');
|
|
230
|
-
const fullCmd = `solana-test-validator ${cmdArgs}`;
|
|
231
|
-
|
|
232
|
-
log.info("👉 Press Ctrl+C to stop it.");
|
|
290
|
+
// --- STEALTH WSL MODE ---
|
|
291
|
+
if (stealth.enabled) {
|
|
292
|
+
const flags = Array.isArray(args) ? args : [];
|
|
233
293
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
});
|
|
239
|
-
return;
|
|
294
|
+
// Unix sockets (admin.rpc) don't work on NTFS-mounted paths (/mnt/c/).
|
|
295
|
+
// Force ledger onto native Linux filesystem.
|
|
296
|
+
if (!flags.some((f) => f.startsWith("--ledger"))) {
|
|
297
|
+
flags.push("--ledger", "/tmp/test-ledger");
|
|
240
298
|
}
|
|
241
299
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
log.error("❌ 'solana-test-validator' is not found in PATH.");
|
|
245
|
-
log.warn("👉 Run 'uso init' to install it.");
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
300
|
+
const cmdArgs = flags.join(" ");
|
|
301
|
+
const fullCmd = `solana-test-validator ${cmdArgs}`;
|
|
248
302
|
|
|
249
|
-
|
|
250
|
-
// or if it's an array of strings (if [args...] is defined)
|
|
251
|
-
// Commander passes (args, options, command) if using [args...]
|
|
252
|
-
// If we change bin/index.js to .command('validator [args...]'), args will be array.
|
|
303
|
+
log.info("👉 Press Ctrl+C to stop it.");
|
|
253
304
|
|
|
254
|
-
const
|
|
305
|
+
const envSetup =
|
|
306
|
+
'source $HOME/.cargo/env 2>/dev/null; export PATH="$HOME/.local/share/solana/install/active_release/bin:$PATH"';
|
|
307
|
+
runWsl(`${envSetup} && ${fullCmd}`, {
|
|
308
|
+
distro: stealth.distro,
|
|
309
|
+
execOpts: { async: false },
|
|
310
|
+
});
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// --- NATIVE MODE ---
|
|
315
|
+
if (!shell.which("solana-test-validator")) {
|
|
316
|
+
log.error("❌ 'solana-test-validator' is not found in PATH.");
|
|
317
|
+
log.warn("👉 Run 'uso init' to install it.");
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Check if args is the commander object (happens if no args defined in command)
|
|
322
|
+
// or if it's an array of strings (if [args...] is defined)
|
|
323
|
+
// Commander passes (args, options, command) if using [args...]
|
|
324
|
+
// If we change bin/index.js to .command('validator [args...]'), args will be array.
|
|
325
|
+
|
|
326
|
+
const flags = Array.isArray(args) ? args : [];
|
|
327
|
+
|
|
328
|
+
// Robust Manual Cleanup for --reset on Windows
|
|
329
|
+
// solana-test-validator --reset often fails with "Access Denied" on Windows due to file locks
|
|
330
|
+
if (flags.includes("--reset") || flags.includes("-r")) {
|
|
331
|
+
const ledgerPath = path.join(process.cwd(), "test-ledger");
|
|
332
|
+
if (fs.existsSync(ledgerPath)) {
|
|
333
|
+
const spin = spinner("🧹 Manually cleaning test-ledger...").start();
|
|
334
|
+
try {
|
|
335
|
+
// Retry loop for Windows file locking
|
|
336
|
+
let retries = 5;
|
|
337
|
+
while (retries > 0) {
|
|
338
|
+
try {
|
|
339
|
+
fs.rmSync(ledgerPath, { recursive: true, force: true });
|
|
340
|
+
if (!fs.existsSync(ledgerPath)) break;
|
|
341
|
+
} catch (e) {
|
|
342
|
+
// wait 500ms
|
|
343
|
+
shell.exec('powershell -Command "Start-Sleep -Milliseconds 500"');
|
|
344
|
+
}
|
|
345
|
+
retries--;
|
|
346
|
+
}
|
|
255
347
|
|
|
256
|
-
// Robust Manual Cleanup for --reset on Windows
|
|
257
|
-
// solana-test-validator --reset often fails with "Access Denied" on Windows due to file locks
|
|
258
|
-
if (flags.includes('--reset') || flags.includes('-r')) {
|
|
259
|
-
const ledgerPath = path.join(process.cwd(), 'test-ledger');
|
|
260
348
|
if (fs.existsSync(ledgerPath)) {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
let retries = 5;
|
|
265
|
-
while (retries > 0) {
|
|
266
|
-
try {
|
|
267
|
-
fs.rmSync(ledgerPath, { recursive: true, force: true });
|
|
268
|
-
if (!fs.existsSync(ledgerPath)) break;
|
|
269
|
-
} catch (e) {
|
|
270
|
-
// wait 500ms
|
|
271
|
-
shell.exec('powershell -Command "Start-Sleep -Milliseconds 500"');
|
|
272
|
-
}
|
|
273
|
-
retries--;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (fs.existsSync(ledgerPath)) {
|
|
277
|
-
// Last resort: ShellJS
|
|
278
|
-
shell.rm('-rf', ledgerPath);
|
|
279
|
-
}
|
|
349
|
+
// Last resort: ShellJS
|
|
350
|
+
shell.rm("-rf", ledgerPath);
|
|
351
|
+
}
|
|
280
352
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
spin.warn(`⚠️ Manual cleanup failed: ${e.message}`);
|
|
288
|
-
}
|
|
353
|
+
if (fs.existsSync(ledgerPath)) {
|
|
354
|
+
spin.warn(
|
|
355
|
+
"⚠️ Could not fully remove test-ledger. Validator might complain.",
|
|
356
|
+
);
|
|
357
|
+
} else {
|
|
358
|
+
spin.succeed("Ledger reset successfully.");
|
|
289
359
|
}
|
|
360
|
+
} catch (e) {
|
|
361
|
+
spin.warn(`⚠️ Manual cleanup failed: ${e.message}`);
|
|
362
|
+
}
|
|
290
363
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const cmdArgs = flags.join(" ");
|
|
367
|
+
const fullCmd = cmdArgs
|
|
368
|
+
? `solana-test-validator ${cmdArgs}`
|
|
369
|
+
: "solana-test-validator";
|
|
370
|
+
|
|
371
|
+
log.info("👉 Press Ctrl+C to stop it.");
|
|
372
|
+
|
|
373
|
+
// Run and capture exit code
|
|
374
|
+
// We use shell.exec, which blocks. If it runs successfully, it blocks until user Ctrl+C.
|
|
375
|
+
// If it fails immediately (like Access Denied), it returns execution object.
|
|
376
|
+
const execution = shell.exec(fullCmd);
|
|
377
|
+
|
|
378
|
+
if (execution.code !== 0) {
|
|
379
|
+
const output = (execution.stderr || "") + (execution.stdout || "");
|
|
380
|
+
// On Windows, exit code 1 with "Access is denied" is common.
|
|
381
|
+
// Also check for "os error 5" or "os error 1314"
|
|
382
|
+
if (
|
|
383
|
+
output.includes("Access is denied") ||
|
|
384
|
+
output.includes("os error 5") ||
|
|
385
|
+
execution.code === 1
|
|
386
|
+
) {
|
|
387
|
+
log.warn(
|
|
388
|
+
"⚠️ Validator failed/crashed. Retrying in specialized Administrator terminal...",
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
// Use Fire-and-Forget for validator (interactive/long-running)
|
|
392
|
+
// We don't want to capture logs, we want the user to see the new window.
|
|
393
|
+
const flagStr = flags.join(" ");
|
|
394
|
+
const targetCmd = `solana-test-validator ${flagStr}`;
|
|
395
|
+
|
|
396
|
+
log.warn(
|
|
397
|
+
"👉 A new window will appear. Keep it open to run the validator!",
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
// Robust Launch using EncodedCommand to avoid quoting hell and set CWD correctly
|
|
401
|
+
// 1. Set CWD (Critical: RunAs defaults to System32)
|
|
402
|
+
// 2. Add Exclusions (Critical: Fixes Access Denied on ledger files)
|
|
403
|
+
// 3. Run Validator
|
|
404
|
+
// 4. Pause on Error
|
|
405
|
+
|
|
406
|
+
const cwd = process.cwd();
|
|
407
|
+
const psScript = `
|
|
325
408
|
$ErrorActionPreference = 'Stop';
|
|
326
409
|
try { Set-Location -Path '${cwd}'; } catch { Write-Host "⚠️ Could not set CWD. Keeping default." -ForegroundColor Yellow; }
|
|
327
410
|
|
|
@@ -358,144 +441,148 @@ const validator = async (args = []) => {
|
|
|
358
441
|
Write-Host "❌ Validator Crashed (Exit Code: $LASTEXITCODE). Press Enter to exit..." -ForegroundColor Red;
|
|
359
442
|
Read-Host;
|
|
360
443
|
}
|
|
361
|
-
|
|
444
|
+
`
|
|
445
|
+
.replace(/\n/g, " ")
|
|
446
|
+
.replace(/\s+/g, " ")
|
|
447
|
+
.trim();
|
|
362
448
|
|
|
363
|
-
|
|
364
|
-
|
|
449
|
+
const encoded = Buffer.from(psScript, "utf16le").toString("base64");
|
|
450
|
+
const psCmd = `powershell -Command "Start-Process powershell -ArgumentList '-NoExit', '-EncodedCommand', '${encoded}' -Verb RunAs"`;
|
|
365
451
|
|
|
366
|
-
|
|
452
|
+
shell.exec(psCmd);
|
|
367
453
|
|
|
368
|
-
|
|
369
|
-
}
|
|
454
|
+
log.success("✅ Validator launch sequence initiated.");
|
|
370
455
|
}
|
|
456
|
+
}
|
|
371
457
|
};
|
|
372
458
|
|
|
373
459
|
const unblock = () => {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}
|
|
460
|
+
const cwd = process.cwd();
|
|
461
|
+
|
|
462
|
+
if (os.platform() !== "win32") {
|
|
463
|
+
log.success("✅ Not on Windows, nothing to unblock.");
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const spin = spinner("Removing Mark-of-the-Web...").start();
|
|
468
|
+
const cargoBin = path.join(os.homedir(), ".cargo", "bin").replace(/'/g, "''");
|
|
469
|
+
const targetCwd = cwd.replace(/'/g, "''");
|
|
470
|
+
|
|
471
|
+
// Use resolved paths to avoid $env variable expansion issues in single quotes
|
|
472
|
+
// And use Where-Object for older PowerShell compatibility
|
|
473
|
+
const cmd = `powershell -Command "Get-ChildItem -Path '${targetCwd}' -Recurse | Where-Object { !$\_.PSIsContainer } | Unblock-File; Get-ChildItem -Path '${cargoBin}' -Recurse | Where-Object { !$\_.PSIsContainer } | Unblock-File"`;
|
|
474
|
+
|
|
475
|
+
const result = shell.exec(cmd, { silent: true });
|
|
476
|
+
|
|
477
|
+
if (result.code === 0) {
|
|
478
|
+
spin.succeed("Files unblocked successfully.");
|
|
479
|
+
log.info("👉 Try running 'uso test' now.");
|
|
480
|
+
} else {
|
|
481
|
+
spin.fail("Failed to unblock files.");
|
|
482
|
+
log.error(result.stderr);
|
|
483
|
+
log.warn("👉 Try running this command as Administrator.");
|
|
484
|
+
}
|
|
400
485
|
};
|
|
401
486
|
|
|
402
487
|
module.exports = {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
488
|
+
build,
|
|
489
|
+
test,
|
|
490
|
+
deploy,
|
|
491
|
+
clean,
|
|
492
|
+
unblock,
|
|
493
|
+
airdrop,
|
|
494
|
+
validator,
|
|
410
495
|
};
|
|
411
496
|
|
|
412
497
|
const dev = async () => {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
// 1. Check if validator is running
|
|
417
|
-
const isValidatorRunning = () => {
|
|
418
|
-
if (stealth.enabled) {
|
|
419
|
-
// Check from Windows side — validator in WSL still binds to host port
|
|
420
|
-
const res = shell.exec('netstat -an | findstr 8899', { silent: true });
|
|
421
|
-
return res.code === 0 && res.stdout.length > 0;
|
|
422
|
-
}
|
|
423
|
-
const res = shell.exec('netstat -an | findstr 8899', { silent: true });
|
|
424
|
-
return res.code === 0 && res.stdout.length > 0;
|
|
425
|
-
};
|
|
426
|
-
|
|
427
|
-
if (isValidatorRunning()) {
|
|
428
|
-
log.success("✅ Validator is already running.");
|
|
429
|
-
} else {
|
|
498
|
+
const stealth = isStealthMode();
|
|
430
499
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
500
|
+
// 1. Check if validator is running
|
|
501
|
+
const isValidatorRunning = () => {
|
|
502
|
+
if (stealth.enabled) {
|
|
503
|
+
// Check from Windows side — validator in WSL still binds to host port
|
|
504
|
+
const res = shell.exec("netstat -an | findstr 8899", { silent: true });
|
|
505
|
+
return res.code === 0 && res.stdout.length > 0;
|
|
506
|
+
}
|
|
507
|
+
const res = shell.exec("netstat -an | findstr 8899", { silent: true });
|
|
508
|
+
return res.code === 0 && res.stdout.length > 0;
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
if (isValidatorRunning()) {
|
|
512
|
+
log.success("✅ Validator is already running.");
|
|
513
|
+
} else {
|
|
514
|
+
// Spawn validator (this will open the Admin window)
|
|
515
|
+
// We use [] args to start cleanly but persistently.
|
|
516
|
+
// If it fails, the user will see it in the new window.
|
|
517
|
+
await validator([]);
|
|
518
|
+
|
|
519
|
+
// Wait for validator to be ready
|
|
520
|
+
const spin = spinner("Waiting for Validator to respond...").start();
|
|
521
|
+
let retries = 60; // 60 seconds (Increased for Windows genesis unpacking)
|
|
522
|
+
while (retries > 0) {
|
|
523
|
+
if (isValidatorRunning()) {
|
|
524
|
+
spin.succeed("✅ Validator is online.");
|
|
525
|
+
break;
|
|
526
|
+
}
|
|
527
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
528
|
+
retries--;
|
|
529
|
+
}
|
|
447
530
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
531
|
+
if (retries === 0) {
|
|
532
|
+
spin.warn(
|
|
533
|
+
"⚠️ Validator might be taking a while to start. Check the blue window.",
|
|
534
|
+
);
|
|
451
535
|
}
|
|
536
|
+
}
|
|
452
537
|
|
|
538
|
+
// Fix: Pass --skip-local-validator directly (without --) so Anchor consumes it
|
|
539
|
+
test(["--skip-local-validator"]);
|
|
453
540
|
|
|
454
|
-
|
|
455
|
-
|
|
541
|
+
log.header("👀 Watching for changes...");
|
|
542
|
+
log.info("👉 Change any .rs or .ts file to re-run tests.");
|
|
543
|
+
log.info("👉 Press Ctrl+C to exit.");
|
|
456
544
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
const runTests = () => {
|
|
463
|
-
if (debounce) return;
|
|
464
|
-
debounce = true;
|
|
465
|
-
setTimeout(() => debounce = false, 2000); // 2s debounce
|
|
466
|
-
|
|
467
|
-
console.clear();
|
|
468
|
-
log.header("🔄 Detected change. Re-running tests...");
|
|
469
|
-
test(['--skip-local-validator']);
|
|
470
|
-
log.header("👀 Watching for changes...");
|
|
471
|
-
};
|
|
545
|
+
let debounce = false;
|
|
546
|
+
const runTests = () => {
|
|
547
|
+
if (debounce) return;
|
|
548
|
+
debounce = true;
|
|
549
|
+
setTimeout(() => (debounce = false), 2000); // 2s debounce
|
|
472
550
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
551
|
+
console.clear();
|
|
552
|
+
log.header("🔄 Detected change. Re-running tests...");
|
|
553
|
+
test(["--skip-local-validator"]);
|
|
554
|
+
log.header("👀 Watching for changes...");
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
// Simple Watcher using fs.watch
|
|
558
|
+
const watchDirs = ["programs", "tests"];
|
|
559
|
+
watchDirs.forEach((dir) => {
|
|
560
|
+
const p = path.join(process.cwd(), dir);
|
|
561
|
+
if (fs.existsSync(p)) {
|
|
562
|
+
fs.watch(p, { recursive: true }, (eventType, filename) => {
|
|
563
|
+
if (
|
|
564
|
+
filename &&
|
|
565
|
+
(filename.endsWith(".rs") || filename.endsWith(".ts"))
|
|
566
|
+
) {
|
|
567
|
+
runTests();
|
|
483
568
|
}
|
|
484
|
-
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
});
|
|
485
572
|
|
|
486
|
-
|
|
487
|
-
|
|
573
|
+
// Keep process alive
|
|
574
|
+
setInterval(() => {}, 1000);
|
|
488
575
|
};
|
|
489
576
|
|
|
490
577
|
module.exports = {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
578
|
+
build,
|
|
579
|
+
test,
|
|
580
|
+
deploy,
|
|
581
|
+
clean,
|
|
582
|
+
unblock,
|
|
583
|
+
airdrop,
|
|
584
|
+
address,
|
|
585
|
+
balance,
|
|
586
|
+
validator,
|
|
587
|
+
dev,
|
|
501
588
|
};
|