mirror of
https://github.com/OpenRouterTeam/spawn.git
synced 2026-05-03 14:20:19 +00:00
refactor: reduce complexity in execScript and netcup pick functions (#449)
Extract error handling from execScript() into dedicated helpers (reportDownloadError, reportScriptFailure, getScriptFailureGuidance), reducing the function from 52 to 15 lines and making error guidance directly testable. Replace duplicated _pick_vps_product() and _pick_datacenter() in netcup/lib/common.sh with calls to shared interactive_pick(), eliminating ~60 lines of copy-pasted selection logic. Net reduction: 42 lines (-98/+56). Agent: complexity-hunter Co-authored-by: A <6723574+louisgv@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
02ecd4123b
commit
4b76b3422c
2 changed files with 56 additions and 98 deletions
|
|
@ -407,6 +407,56 @@ function reportDownloadFailure(primaryUrl: string, fallbackUrl: string, primaryS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function reportDownloadError(ghUrl: string, err: unknown): never {
|
||||||
|
p.log.error("Failed to download spawn script");
|
||||||
|
console.error("\nError:", getErrorMessage(err));
|
||||||
|
console.error("\nTroubleshooting:");
|
||||||
|
console.error(` 1. Verify this combination exists: ${pc.cyan("spawn list")}`);
|
||||||
|
console.error(" 2. Check your internet connection");
|
||||||
|
console.error(` 3. Try accessing the script directly: ${ghUrl}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getScriptFailureGuidance(exitCode: number | null, cloud: string): string[] {
|
||||||
|
switch (exitCode) {
|
||||||
|
case 127:
|
||||||
|
return [
|
||||||
|
"A required command was not found. Check that these are installed:",
|
||||||
|
" - bash, curl, ssh, jq",
|
||||||
|
` - Cloud-specific CLI tools (run ${pc.cyan(`spawn ${cloud}`)} for details)`,
|
||||||
|
];
|
||||||
|
case 126:
|
||||||
|
return ["A command was found but could not be executed (permission denied)."];
|
||||||
|
case 1:
|
||||||
|
return [
|
||||||
|
"Common causes:",
|
||||||
|
` - Missing or invalid credentials (run ${pc.cyan(`spawn ${cloud}`)} for setup)`,
|
||||||
|
" - Cloud provider API error (quota, rate limit, or region issue)",
|
||||||
|
" - Server provisioning failed (try again or pick a different region)",
|
||||||
|
];
|
||||||
|
default:
|
||||||
|
return [
|
||||||
|
"Common causes:",
|
||||||
|
` - Missing credentials (run ${pc.cyan(`spawn ${cloud}`)} for setup instructions)`,
|
||||||
|
" - Cloud provider API rate limit or quota exceeded",
|
||||||
|
" - Missing local dependencies (SSH, curl, jq)",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function reportScriptFailure(errMsg: string, cloud: string): never {
|
||||||
|
p.log.error("Spawn script failed");
|
||||||
|
console.error("\nError:", errMsg);
|
||||||
|
|
||||||
|
const exitCodeMatch = errMsg.match(/exited with code (\d+)/);
|
||||||
|
const exitCode = exitCodeMatch ? parseInt(exitCodeMatch[1], 10) : null;
|
||||||
|
|
||||||
|
const lines = getScriptFailureGuidance(exitCode, cloud);
|
||||||
|
console.error("");
|
||||||
|
for (const line of lines) console.error(line);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
async function execScript(cloud: string, agent: string, prompt?: string): Promise<void> {
|
async function execScript(cloud: string, agent: string, prompt?: string): Promise<void> {
|
||||||
const url = `https://openrouter.ai/lab/spawn/${cloud}/${agent}.sh`;
|
const url = `https://openrouter.ai/lab/spawn/${cloud}/${agent}.sh`;
|
||||||
const ghUrl = `${RAW_BASE}/${cloud}/${agent}.sh`;
|
const ghUrl = `${RAW_BASE}/${cloud}/${agent}.sh`;
|
||||||
|
|
@ -415,13 +465,7 @@ async function execScript(cloud: string, agent: string, prompt?: string): Promis
|
||||||
try {
|
try {
|
||||||
scriptContent = await downloadScriptWithFallback(url, ghUrl);
|
scriptContent = await downloadScriptWithFallback(url, ghUrl);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
p.log.error("Failed to download spawn script");
|
reportDownloadError(ghUrl, err);
|
||||||
console.error("\nError:", getErrorMessage(err));
|
|
||||||
console.error("\nTroubleshooting:");
|
|
||||||
console.error(` 1. Verify this combination exists: ${pc.cyan("spawn list")}`);
|
|
||||||
console.error(" 2. Check your internet connection");
|
|
||||||
console.error(` 3. Try accessing the script directly: ${ghUrl}`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
@ -429,34 +473,9 @@ async function execScript(cloud: string, agent: string, prompt?: string): Promis
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const errMsg = getErrorMessage(err);
|
const errMsg = getErrorMessage(err);
|
||||||
if (errMsg.includes("interrupted by user")) {
|
if (errMsg.includes("interrupted by user")) {
|
||||||
// User pressed Ctrl+C - exit silently
|
|
||||||
process.exit(130);
|
process.exit(130);
|
||||||
}
|
}
|
||||||
p.log.error("Spawn script failed");
|
reportScriptFailure(errMsg, cloud);
|
||||||
console.error("\nError:", errMsg);
|
|
||||||
|
|
||||||
// Extract exit code from error message for targeted guidance
|
|
||||||
const exitCodeMatch = errMsg.match(/exited with code (\d+)/);
|
|
||||||
const exitCode = exitCodeMatch ? parseInt(exitCodeMatch[1], 10) : null;
|
|
||||||
|
|
||||||
if (exitCode === 127) {
|
|
||||||
console.error("\nA required command was not found. Check that these are installed:");
|
|
||||||
console.error(" - bash, curl, ssh, jq");
|
|
||||||
console.error(` - Cloud-specific CLI tools (run ${pc.cyan(`spawn ${cloud}`)} for details)`);
|
|
||||||
} else if (exitCode === 126) {
|
|
||||||
console.error("\nA command was found but could not be executed (permission denied).");
|
|
||||||
} else if (exitCode === 1) {
|
|
||||||
console.error("\nCommon causes:");
|
|
||||||
console.error(` - Missing or invalid credentials (run ${pc.cyan(`spawn ${cloud}`)} for setup)`);
|
|
||||||
console.error(" - Cloud provider API error (quota, rate limit, or region issue)");
|
|
||||||
console.error(" - Server provisioning failed (try again or pick a different region)");
|
|
||||||
} else {
|
|
||||||
console.error("\nCommon causes:");
|
|
||||||
console.error(` - Missing credentials (run ${pc.cyan(`spawn ${cloud}`)} for setup instructions)`);
|
|
||||||
console.error(" - Cloud provider API rate limit or quota exceeded");
|
|
||||||
console.error(" - Missing local dependencies (SSH, curl, jq)");
|
|
||||||
}
|
|
||||||
process.exit(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -223,43 +223,9 @@ for p in sorted(products, key=lambda x: float(x.get('price', 999))):
|
||||||
"
|
"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Interactive VPS product picker
|
# Interactive VPS product picker (delegates to shared interactive_pick)
|
||||||
_pick_vps_product() {
|
_pick_vps_product() {
|
||||||
if [[ -n "${NETCUP_VPS_PRODUCT:-}" ]]; then
|
interactive_pick "NETCUP_VPS_PRODUCT" "VPS 200 G10" "VPS products" "_list_vps_products"
|
||||||
echo "$NETCUP_VPS_PRODUCT"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
log_info "Fetching available VPS products..."
|
|
||||||
local products
|
|
||||||
products=$(_list_vps_products)
|
|
||||||
|
|
||||||
if [[ -z "$products" ]]; then
|
|
||||||
log_warn "Could not fetch VPS products, using default: VPS 200 G10"
|
|
||||||
echo "VPS 200 G10"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
log_info "Available VPS products:"
|
|
||||||
local i=1
|
|
||||||
local names=()
|
|
||||||
while IFS='|' read -r name cores ram disk price; do
|
|
||||||
printf " %2d) %-15s %-8s %-12s %-13s %s\n" "$i" "$name" "$cores" "$ram" "$disk" "$price" >&2
|
|
||||||
names+=("$name")
|
|
||||||
i=$((i + 1))
|
|
||||||
done <<< "$products"
|
|
||||||
|
|
||||||
local choice
|
|
||||||
printf "\n" >&2
|
|
||||||
choice=$(safe_read "Select VPS product [1]: ") || choice=""
|
|
||||||
choice="${choice:-1}"
|
|
||||||
|
|
||||||
if [[ "$choice" -ge 1 && "$choice" -le "${#names[@]}" ]] 2>/dev/null; then
|
|
||||||
echo "${names[$((choice - 1))]}"
|
|
||||||
else
|
|
||||||
log_warn "Invalid choice, using default: VPS 200 G10"
|
|
||||||
echo "VPS 200 G10"
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# List available datacenters
|
# List available datacenters
|
||||||
|
|
@ -269,36 +235,9 @@ _list_datacenters() {
|
||||||
echo "Vienna|AT|Austria"
|
echo "Vienna|AT|Austria"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Interactive datacenter picker
|
# Interactive datacenter picker (delegates to shared interactive_pick)
|
||||||
_pick_datacenter() {
|
_pick_datacenter() {
|
||||||
if [[ -n "${NETCUP_DATACENTER:-}" ]]; then
|
interactive_pick "NETCUP_DATACENTER" "Nuremberg" "datacenters" "_list_datacenters"
|
||||||
echo "$NETCUP_DATACENTER"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
log_info "Available datacenters:"
|
|
||||||
local datacenters
|
|
||||||
datacenters=$(_list_datacenters)
|
|
||||||
|
|
||||||
local i=1
|
|
||||||
local names=()
|
|
||||||
while IFS='|' read -r name country_code country; do
|
|
||||||
printf " %2d) %-12s %s (%s)\n" "$i" "$name" "$country" "$country_code" >&2
|
|
||||||
names+=("$name")
|
|
||||||
i=$((i + 1))
|
|
||||||
done <<< "$datacenters"
|
|
||||||
|
|
||||||
local choice
|
|
||||||
printf "\n" >&2
|
|
||||||
choice=$(safe_read "Select datacenter [1]: ") || choice=""
|
|
||||||
choice="${choice:-1}"
|
|
||||||
|
|
||||||
if [[ "$choice" -ge 1 && "$choice" -le "${#names[@]}" ]] 2>/dev/null; then
|
|
||||||
echo "${names[$((choice - 1))]}"
|
|
||||||
else
|
|
||||||
log_warn "Invalid choice, using default: Nuremberg"
|
|
||||||
echo "Nuremberg"
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Build JSON request body for Netcup VPS creation
|
# Build JSON request body for Netcup VPS creation
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue