Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 50 additions & 9 deletions docs/install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,40 @@ if (-not (Get-Command npm -ErrorAction SilentlyContinue)) {
Write-Err 'npm is not available. Install npm and try again.'
}

Write-Info "Installing $PackageName globally..."
npm install -g $PackageName
$UserPrefix = $null

# Run npm install without aborting on failure, so we can fall back. Returns the exit code.
function Invoke-NpmInstall {
param([string[]]$NpmArgs)
try {
& npm install -g @NpmArgs | Out-Host
return $LASTEXITCODE
} catch {
return 1
}
}

function Install-Package {
Write-Info "Installing $PackageName globally..."
if ((Invoke-NpmInstall @($PackageName)) -eq 0) {
return
}

Write-Warn 'Global install failed. Falling back to a user-level install.'
$script:UserPrefix = Join-Path $env:APPDATA 'npm-global'
New-Item -ItemType Directory -Force -Path $script:UserPrefix | Out-Null
Write-Info "Installing $PackageName to $script:UserPrefix instead..."
if ((Invoke-NpmInstall @('--prefix', $script:UserPrefix, $PackageName)) -ne 0) {
Write-Err @"
Installation failed.
Try fixing your npm permissions, or run the CLI without installing: npx $PackageName --help
"@
}

$env:PATH = "$script:UserPrefix;$env:PATH"
}

Install-Package

$installedVersion = $null
if (Get-Command $CommandName -ErrorAction SilentlyContinue) {
Expand All @@ -65,17 +97,26 @@ if ($installedVersion) {
Write-Host ''
Write-Host 'Installed! You may need to restart your shell or add the npm global bin directory to your PATH.' -ForegroundColor Green

$npmPrefix = (npm prefix -g 2>$null).Trim()
if ($npmPrefix) {
$pathEntries = $env:PATH -split ';' | Where-Object { $_ -ne '' }
if ($pathEntries -notcontains $npmPrefix) {
Write-Warn "$npmPrefix is not in your PATH. Add it with:"
Write-Host " setx PATH `"$npmPrefix;%PATH%`""
Write-Host ''
if (-not $UserPrefix) {
$npmPrefix = (npm prefix -g 2>$null).Trim()
if ($npmPrefix) {
$pathEntries = $env:PATH -split ';' | Where-Object { $_ -ne '' }
if ($pathEntries -notcontains $npmPrefix) {
Write-Warn "$npmPrefix is not in your PATH. Add it with:"
Write-Host " setx PATH `"$npmPrefix;%PATH%`""
Write-Host ''
}
}
}
}

if ($UserPrefix) {
Write-Host ''
Write-Host "The CLI was installed to $UserPrefix."
Write-Host 'Add it to your PATH permanently with:'
Write-Host " setx PATH `"$UserPrefix;%PATH%`""
}

Write-Host ''
Write-Host 'Next step: configure your auth token with decodo setup'
Write-Host 'Get started:'
Expand Down
62 changes: 55 additions & 7 deletions docs/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,47 @@ Update Node.js from https://nodejs.org/ and try again."
echo "$version"
}

can_write_global() {
prefix=$(npm prefix -g 2>/dev/null) || return 1
[ -n "$prefix" ] || return 1

for dir in "${prefix}/lib/node_modules" "${prefix}/bin"; do
target="$dir"
while [ ! -d "$target" ]; do
target=$(dirname "$target")
done
[ -w "$target" ] || return 1
done
}

install_package() {
USER_PREFIX_BIN=""

if can_write_global; then
info "Installing ${PACKAGE_NAME} globally..."
if npm install -g "${PACKAGE_NAME}"; then
return
fi
warn "Global install failed. Falling back to a user-level install."
else
warn "No write permission for the npm global directory ($(npm prefix -g 2>/dev/null))."
info "Installing ${PACKAGE_NAME} to ${HOME}/.npm-global instead (no sudo needed)..."
fi

user_prefix="${HOME}/.npm-global"
mkdir -p "$user_prefix"

if ! npm install -g --prefix "$user_prefix" "${PACKAGE_NAME}"; then
error "Installation failed.
Try fixing your npm permissions (https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally)
or run the CLI without installing: npx ${PACKAGE_NAME} --help"
fi

USER_PREFIX_BIN="${user_prefix}/bin"
PATH="${USER_PREFIX_BIN}:${PATH}"
export PATH
}

main() {
printf "\n${BOLD}Decodo CLI Installer${RESET}\n\n"

Expand All @@ -59,22 +100,29 @@ main() {
error "npm is not available. Install npm and try again."
fi

info "Installing ${PACKAGE_NAME} globally..."
npm install -g "${PACKAGE_NAME}"
install_package

if command -v "$COMMAND_NAME" >/dev/null 2>&1; then
installed_version=$("$COMMAND_NAME" --version 2>/dev/null || echo "unknown")
printf "\n${GREEN}${BOLD}Success!${RESET} ${PACKAGE_NAME} ${installed_version} is installed.\n"
else
printf "\n${GREEN}${BOLD}Installed!${RESET} You may need to restart your shell or add the npm global bin directory to your PATH.\n"
npm_prefix=$(npm config get prefix 2>/dev/null) || true
npm_bin="${npm_prefix:+$npm_prefix/bin}"
if [ -n "$npm_bin" ] && ! echo "$PATH" | tr ':' '\n' | grep -qx "$npm_bin"; then
warn "${npm_bin} is not in your PATH. Add it with:"
printf " export PATH=\"%s:\$PATH\"\n\n" "$npm_bin"
if [ -z "$USER_PREFIX_BIN" ]; then
npm_prefix=$(npm config get prefix 2>/dev/null) || true
npm_bin="${npm_prefix:+$npm_prefix/bin}"
if [ -n "$npm_bin" ] && ! echo "$PATH" | tr ':' '\n' | grep -qx "$npm_bin"; then
warn "${npm_bin} is not in your PATH. Add it with:"
printf " export PATH=\"%s:\$PATH\"\n\n" "$npm_bin"
fi
fi
fi

if [ -n "$USER_PREFIX_BIN" ]; then
printf "\nThe CLI was installed to ${BOLD}%s${RESET}.\n" "$USER_PREFIX_BIN"
printf "Add it to your PATH permanently by appending this line to your shell profile (e.g. ~/.zshrc or ~/.bashrc):\n"
printf " ${BOLD}export PATH=\"%s:\$PATH\"${RESET}\n" "$USER_PREFIX_BIN"
fi

printf "\nNext step: configure your auth token with ${BOLD}decodo setup${RESET}\n"
printf "Get started:\n"
printf " ${BOLD}decodo scrape${RESET} https://ip.decodo.com\n"
Expand Down
80 changes: 40 additions & 40 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions src/auth/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export const PLAYGROUND_URL = "https://dashboard.decodo.com/playground";

export const AUTH_MISSING_MESSAGE =
"No auth token found. Run `decodo setup` or set DECODO_AUTH_TOKEN.";
export const AUTH_MISSING_MESSAGE = "No auth token found.";
10 changes: 9 additions & 1 deletion src/platform/services/handle-cli-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
TimeoutError,
ValidationError,
} from "@decodo/sdk-ts";
import { PLAYGROUND_URL } from "../../auth/constants.js";
import { AuthRequiredError } from "../../auth/errors/auth-required-error.js";
import { EXIT } from "../constants.js";

Expand Down Expand Up @@ -113,7 +114,14 @@ export function handleCliError(
}
}

if (err instanceof AuthRequiredError || err instanceof AuthenticationError) {
if (err instanceof AuthRequiredError) {
console.error(
"\nThe Decodo CLI is installed and working - it just needs an auth token:\n" +
` 1. Get your Web Scraping API token at ${PLAYGROUND_URL}\n` +
" 2. Run `decodo setup` to save it (or set DECODO_AUTH_TOKEN)\n" +
" 3. Re-run your command"
);
} else if (err instanceof AuthenticationError) {
console.error("Hint: Run `decodo setup` to configure your auth token.");
}

Expand Down
2 changes: 1 addition & 1 deletion src/scrape/services/auth-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ export async function validateAuthToken(token: string): Promise<void> {

await client.webScrapingApi.scrape({
target: ScrapeTarget.Universal,
url: "https://ip.decodo.com",
url: "https://does-not-exist.decodo.com",
Comment thread
ChinchillaLover9000 marked this conversation as resolved.
});
}
7 changes: 5 additions & 2 deletions tests/platform/services/handle-cli-error.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,24 @@ describe("handleCliError", () => {
vi.restoreAllMocks();
});

it("maps auth-required errors to exit code 3 with setup hint", () => {
it("maps auth-required errors to exit code 3 with onboarding steps", () => {
expect(() =>
handleCliError(new AuthRequiredError("token missing"))
).toThrow("process.exit:3");

expect(exitCode).toBe(3);
expect(stderr.join("\n")).toContain("token missing");
expect(stderr.join("\n")).toContain("installed and working");
expect(stderr.join("\n")).toContain("decodo setup");
expect(stderr.join("\n")).toContain("DECODO_AUTH_TOKEN");
});

it("maps SDK authentication errors to exit code 3", () => {
it("maps SDK authentication errors to exit code 3 with setup hint", () => {
const err = new AuthenticationError("Unauthorized");

expect(() => handleCliError(err)).toThrow("process.exit:3");
expect(exitCode).toBe(3);
expect(stderr.join("\n")).toContain("decodo setup");
});

it("maps validation errors to exit code 4 and prints details", () => {
Expand Down
Loading
Loading