@decodo/cli
Advanced tools
| export declare class CliUsageError extends Error { | ||
| constructor(message: string); | ||
| } |
| export class CliUsageError extends Error { | ||
| constructor(message) { | ||
| super(message); | ||
| this.name = "CliUsageError"; | ||
| } | ||
| } |
| import { Command } from "commander"; | ||
| import { getRootOpts } from "../../cli/services/global-opts.js"; | ||
| import { CliUsageError, handleCliError, } from "../../platform/services/handle-cli-error.js"; | ||
| import { CliUsageError } from "../../platform/errors/cli-usage-error.js"; | ||
| import { handleCliError } from "../../platform/services/handle-cli-error.js"; | ||
| import { promptHidden } from "../../platform/services/prompt-hidden.js"; | ||
@@ -5,0 +6,0 @@ import { validateAuthToken } from "../../scrape/services/auth-validation.js"; |
| export function applyRequestDefaults(body, target, schema) { | ||
| if (body.headless === "png") { | ||
| return; | ||
| } | ||
| const properties = schema.getTargetParameterSchema(target)?.properties ?? {}; | ||
@@ -3,0 +6,0 @@ if (properties.parse !== undefined && body.parse === undefined) { |
@@ -1,4 +0,1 @@ | ||
| export declare class CliUsageError extends Error { | ||
| constructor(message: string); | ||
| } | ||
| export declare function resolveCliExitCode(err: unknown): number; | ||
@@ -5,0 +2,0 @@ export declare function handleCliError(err: unknown, options?: { |
@@ -5,8 +5,27 @@ import { AuthenticationError, DecodoError, RateLimitError, TimeoutError, ValidationError, } from "@decodo/sdk-ts"; | ||
| import { EXIT } from "../constants.js"; | ||
| import { CliUsageError } from "../errors/cli-usage-error.js"; | ||
| const EXIT_SIGNAL_PREFIX = "process.exit:"; | ||
| export class CliUsageError extends Error { | ||
| constructor(message) { | ||
| super(message); | ||
| this.name = "CliUsageError"; | ||
| const NETWORK_ERROR_CODES = new Set([ | ||
| "ENOTFOUND", | ||
| "ECONNREFUSED", | ||
| "ECONNRESET", | ||
| "ETIMEDOUT", | ||
| "EAI_AGAIN", | ||
| "EHOSTUNREACH", | ||
| "ENETUNREACH", | ||
| "EPIPE", | ||
| ]); | ||
| function findNetworkCause(err) { | ||
| const seen = new Set(); | ||
| let current = err; | ||
| while (current && typeof current === "object" && !seen.has(current)) { | ||
| seen.add(current); | ||
| const code = current.code; | ||
| if (typeof code === "string" && NETWORK_ERROR_CODES.has(code)) { | ||
| const message = current.message; | ||
| return { code, message: typeof message === "string" ? message : code }; | ||
| } | ||
| current = current.cause; | ||
| } | ||
| return; | ||
| } | ||
@@ -32,2 +51,5 @@ export function resolveCliExitCode(err) { | ||
| } | ||
| if (findNetworkCause(err)) { | ||
| return EXIT.NETWORK; | ||
| } | ||
| return EXIT.ERROR; | ||
@@ -76,2 +98,6 @@ } | ||
| console.error(`Error: ${message}`); | ||
| const networkCause = findNetworkCause(err); | ||
| if (networkCause) { | ||
| console.error(`Cause: ${networkCause.code} (${networkCause.message})`); | ||
| } | ||
| if (err instanceof ValidationError) { | ||
@@ -78,0 +104,0 @@ const details = extractValidationDetails(err); |
| import { stdin, stdout } from "node:process"; |
| import { createInterface } from "node:readline/promises"; |
| import { CliUsageError } from "../errors/cli-usage-error.js"; |
| const CHAR_ETX = 3; |
| const CHAR_EOT = 4; |
| const CHAR_DEL = 127; |
| const CHAR_BACKSPACE = 8; |
| function handleHiddenPromptChar(char, state) { |
| if (char === "\u0003") { |
| const code = char.charCodeAt(0); |
| if (code === CHAR_ETX) { |
| state.cleanup(); |
@@ -10,2 +16,8 @@ stdout.write("\n"); |
| } |
| if (code === CHAR_EOT) { |
| state.cleanup(); |
| stdout.write("\n"); |
| state.reject(new CliUsageError("No auth token provided on stdin.")); |
| return; |
| } |
| if (char === "\r" || char === "\n") { |
@@ -17,3 +29,3 @@ state.cleanup(); |
| } |
| if (char === "\u007f" || char === "\b") { |
| if (code === CHAR_DEL || code === CHAR_BACKSPACE) { |
| if (state.input.length > 0) { |
@@ -27,11 +39,19 @@ state.input = state.input.slice(0, -1); |
| } |
| async function promptViaReadline(message) { |
| const rl = createInterface({ input: stdin, output: stdout }); |
| try { |
| return await new Promise((resolve, reject) => { |
| rl.question(message).then((answer) => resolve(answer.trim()), reject); |
| rl.once("close", () => { |
| reject(new CliUsageError("No auth token provided on stdin.")); |
| }); |
| }); |
| } |
| finally { |
| rl.close(); |
| } |
| } |
| export async function promptHidden(message) { |
| if (!stdin.isTTY) { |
| const rl = createInterface({ input: stdin, output: stdout }); |
| try { |
| return (await rl.question(message)).trim(); |
| } |
| finally { |
| rl.close(); |
| } |
| return await promptViaReadline(message); |
| } |
@@ -54,2 +74,7 @@ stdout.write(message); |
| }; |
| const onEnd = () => { |
| state.cleanup(); |
| stdout.write("\n"); |
| reject(new CliUsageError("No auth token provided on stdin.")); |
| }; |
| state.cleanup = () => { |
@@ -59,5 +84,7 @@ stdin.setRawMode(false); |
| stdin.removeListener("data", onData); |
| stdin.removeListener("end", onEnd); |
| }; |
| stdin.on("data", onData); |
| stdin.on("end", onEnd); |
| }); |
| } |
| import { writeFileSync } from "node:fs"; | ||
| import { resolve } from "node:path"; | ||
| import { CliUsageError, handleCliError } from "./handle-cli-error.js"; | ||
| import { CliUsageError } from "../errors/cli-usage-error.js"; | ||
| import { handleCliError } from "./handle-cli-error.js"; | ||
| import { resolveOutputFilePath } from "./resolve-output-file.js"; | ||
@@ -5,0 +6,0 @@ export const BINARY_TTY_ERROR = "Refusing to write binary data to a TTY. Use -o <file> or pipe to a file."; |
@@ -1,2 +0,2 @@ | ||
| import { Option } from "commander"; | ||
| import { InvalidArgumentError, Option } from "commander"; | ||
| import { attachScrapeOutputOptions } from "../../output/commands/attach-output-options.js"; | ||
@@ -20,4 +20,20 @@ import { applyRequestDefaults } from "../../output/services/apply-request-defaults.js"; | ||
| } | ||
| if (propertySchema.type === "array") { | ||
| return "JSON array"; | ||
| } | ||
| if (propertySchema.type === "object") { | ||
| return "JSON object"; | ||
| } | ||
| return String(propertySchema.type ?? "value"); | ||
| } | ||
| function parseJsonArg(field) { | ||
| return (value) => { | ||
| try { | ||
| return JSON.parse(value); | ||
| } | ||
| catch { | ||
| throw new InvalidArgumentError(`--${snakeToKebab(field)} expects valid JSON.`); | ||
| } | ||
| }; | ||
| } | ||
| function addPropertyOption(command, field, propertySchema) { | ||
@@ -42,2 +58,6 @@ const kebabFlag = snakeToKebab(field); | ||
| } | ||
| if (propertySchema.type === "array" || propertySchema.type === "object") { | ||
| command.option(`--${kebabFlag} <json>`, help, parseJsonArg(field)); | ||
| return; | ||
| } | ||
| command.option(`--${kebabFlag} <value>`, help); | ||
@@ -44,0 +64,0 @@ } |
@@ -21,2 +21,11 @@ import { AuthRequiredError } from "../../auth/errors/auth-required-error.js"; | ||
| } | ||
| function resolveOutputContext(explicit, body, input) { | ||
| if (explicit?.binary) { | ||
| return explicit; | ||
| } | ||
| if (body.headless === "png") { | ||
| return { ...explicit, binary: { kind: "png" }, input }; | ||
| } | ||
| return explicit; | ||
| } | ||
| export function createTargetAction(target, schema, buildBody, getOutputContext) { | ||
@@ -47,3 +56,3 @@ const config = getTargetCommandConfig(target, schema); | ||
| verboseLog(verbose, formatScrapeRequestLog(body)); | ||
| const outputContext = getOutputContext?.(input, options); | ||
| const outputContext = resolveOutputContext(getOutputContext?.(input, options), body, input); | ||
| await executeScrape({ | ||
@@ -50,0 +59,0 @@ token: auth.token, |
+1
-1
| { | ||
| "name": "@decodo/cli", | ||
| "version": "0.1.6", | ||
| "version": "0.1.7", | ||
| "description": "Official CLI for the Decodo APIs", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
+1
-1
@@ -283,3 +283,3 @@ # Decodo CLI | ||
| Ensure npm's global bin directory is on your `PATH` after `npm install -g`. Re-run the [install script](https://decodo.github.io/cli/install.sh) or use `npx @decodo/cli`. | ||
| The [install script](https://decodo.github.io/cli/install.sh) auto-configures PATH and prints a `source` step — re-run it or open a new terminal. You can also use `npx @decodo/cli`. | ||
@@ -286,0 +286,0 @@ **Validation / API errors** |
66184
4.65%117
1.74%1390
7.17%