From 855197f2c297c4f14fbed7db9f11607cc89a1514 Mon Sep 17 00:00:00 2001 From: patrikbraborec Date: Tue, 23 Jun 2026 14:59:08 +0200 Subject: [PATCH 1/3] fix(call): improve input help UX Co-authored-by: Cursor --- docs/reference.md | 6 ++- src/commands/actors/call.ts | 19 +++++++-- src/lib/commands/resolve-input.ts | 61 ++++++++++++++++++++++++---- test/local/lib/resolve-input.test.ts | 55 +++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 15 deletions(-) create mode 100644 test/local/lib/resolve-input.test.ts diff --git a/docs/reference.md b/docs/reference.md index 59fcba11d..6f27779ea 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -782,6 +782,7 @@ FLAGS DESCRIPTION Executes Actor remotely using your authenticated account. Reads input from local key-value store by default. + Inspect the input schema first with "apify actors info --input". USAGE $ apify actors call [actorId] [-b ] @@ -797,8 +798,9 @@ ARGUMENTS FLAGS -b, --build= Tag or number of the build to run (e.g. "latest" or "1.2.34"). - -i, --input= Optional JSON input to be - given to the Actor. + -i, --input= Optional inline JSON object + input for the Actor. Wrap the JSON in quotes to avoid + shell parsing issues. For JSON files, use --input-file. -f, --input-file= Optional path to a file with JSON input to be given to the Actor. The file must be a valid JSON file. You can also specify `-` to read from diff --git a/src/commands/actors/call.ts b/src/commands/actors/call.ts index 451a1ab11..98279f2f9 100644 --- a/src/commands/actors/call.ts +++ b/src/commands/actors/call.ts @@ -30,7 +30,8 @@ export class ActorsCallCommand extends ApifyCommand { static override description = 'Executes Actor remotely using your authenticated account.\n' + - 'Reads input from local key-value store by default.'; + 'Reads input from local key-value store by default.\n' + + 'Inspect the input schema first with "apify actors info --input".'; static override group = 'Apify Console'; @@ -43,6 +44,10 @@ export class ActorsCallCommand extends ApifyCommand { description: 'Call a specific Actor by its full name.', command: 'apify call apify/hello-world', }, + { + description: 'Inspect the Actor input schema before preparing JSON input.', + command: 'apify actors info apify/hello-world --input', + }, { description: 'Call an Actor with inline JSON input.', command: `apify call apify/hello-world --input '{"url":"https://example.com"}'`, @@ -59,7 +64,8 @@ export class ActorsCallCommand extends ApifyCommand { ...SharedRunOnCloudFlags('Actor'), input: Flags.string({ char: 'i', - description: 'Optional JSON input to be given to the Actor.', + description: + 'Optional inline JSON object input for the Actor. Wrap the JSON in quotes to avoid shell parsing issues. For JSON files, use --input-file.', required: false, stdin: StdinMode.Stringified, exclusive: ['input-file'], @@ -103,7 +109,10 @@ export class ActorsCallCommand extends ApifyCommand { const usernameOrId = userInfo.username || (userInfo.id as string); if (this.flags.json && this.flags.outputDataset) { - error({ message: 'You cannot use both the --json and --output-dataset flags when running this command.' }); + error({ + message: + 'You cannot use both --json and --output-dataset. Use --json for run details or --output-dataset for dataset items.', + }); process.exitCode = CommandExitCodes.InvalidInput; return; @@ -136,7 +145,9 @@ export class ActorsCallCommand extends ApifyCommand { runOpts.memory = this.flags.memory; } - const inputOverride = await getInputOverride(cwd, this.flags.input, this.flags.inputFile); + const inputOverride = await getInputOverride(cwd, this.flags.input, this.flags.inputFile, { + schemaHint: `Run "apify actors info ${userFriendlyId} --input" to inspect the Actor input schema.`, + }); // Means we couldn't resolve input, so we should exit if (inputOverride === false) { diff --git a/src/lib/commands/resolve-input.ts b/src/lib/commands/resolve-input.ts index 95ee82ae8..246fd0550 100644 --- a/src/lib/commands/resolve-input.ts +++ b/src/lib/commands/resolve-input.ts @@ -9,6 +9,14 @@ import { CommandExitCodes } from '../consts.js'; import { error } from '../outputs.js'; import { getLocalInput } from '../utils.js'; +interface InputOverrideOptions { + schemaHint?: string; +} + +function withSchemaHint(message: string, schemaHint?: string) { + return schemaHint ? `${message}\n${schemaHint}` : message; +} + export function resolveInput(cwd: string, inputOverride: Record | undefined) { let inputToUse: Record | undefined; let contentType!: string; @@ -39,9 +47,15 @@ export function resolveInput(cwd: string, inputOverride: Record return { inputToUse, contentType }; } -export async function getInputOverride(cwd: string, inputFlag: string | undefined, inputFileFlag: string | undefined) { +export async function getInputOverride( + cwd: string, + inputFlag: string | undefined, + inputFileFlag: string | undefined, + options: InputOverrideOptions = {}, +) { let input: Record | undefined; let source: 'stdin' | 'input' | string; + const { schemaHint } = options; if (!inputFlag && !inputFileFlag) { // Try reading stdin @@ -52,7 +66,9 @@ export async function getInputOverride(cwd: string, inputFlag: string | undefine const parsed = JSON.parse(stdin.toString('utf8')); if (Array.isArray(parsed)) { - error({ message: 'The provided input is invalid. It should be an object, not an array.' }); + error({ + message: withSchemaHint('The provided input is invalid. It should be an object, not an array.', schemaHint), + }); process.exitCode = CommandExitCodes.InvalidInput; return false; } @@ -60,7 +76,12 @@ export async function getInputOverride(cwd: string, inputFlag: string | undefine input = parsed; source = 'stdin'; } catch (err) { - error({ message: `Cannot parse JSON input from standard input.\n ${(err as Error).message}` }); + error({ + message: withSchemaHint( + `Cannot parse JSON input from standard input.\n ${(err as Error).message}`, + schemaHint, + ), + }); process.exitCode = CommandExitCodes.InvalidInput; return false; } @@ -98,7 +119,10 @@ export async function getInputOverride(cwd: string, inputFlag: string | undefine if (fileExists || inputLooksLikePath) { error({ - message: `Providing a JSON file path in the --input flag is not supported. Use the "--input-file=" flag instead`, + message: withSchemaHint( + `Providing a JSON file path in the --input flag is not supported. Use the "--input-file=" flag instead`, + schemaHint, + ), }); process.exitCode = CommandExitCodes.InvalidInput; return false; @@ -108,7 +132,12 @@ export async function getInputOverride(cwd: string, inputFlag: string | undefine const parsed = JSON.parse(inputFlag); if (Array.isArray(parsed)) { - error({ message: 'The provided input is invalid. It should be an object, not an array.' }); + error({ + message: withSchemaHint( + 'The provided input is invalid. It should be an object, not an array.', + schemaHint, + ), + }); process.exitCode = CommandExitCodes.InvalidInput; return false; } @@ -116,7 +145,9 @@ export async function getInputOverride(cwd: string, inputFlag: string | undefine input = parsed; source = 'input'; } catch (err) { - error({ message: `Cannot parse JSON input.\n ${(err as Error).message}` }); + error({ + message: withSchemaHint(`Cannot parse JSON input.\n ${(err as Error).message}`, schemaHint), + }); process.exitCode = CommandExitCodes.InvalidInput; return false; } @@ -143,7 +174,12 @@ export async function getInputOverride(cwd: string, inputFlag: string | undefine const parsed = JSON.parse(fileContent); if (Array.isArray(parsed)) { - error({ message: 'The provided input is invalid. It should be an object, not an array.' }); + error({ + message: withSchemaHint( + 'The provided input is invalid. It should be an object, not an array.', + schemaHint, + ), + }); process.exitCode = CommandExitCodes.InvalidInput; return false; } @@ -159,7 +195,12 @@ export async function getInputOverride(cwd: string, inputFlag: string | undefine const parsed = JSON.parse(inputFileFlag); if (Array.isArray(parsed)) { - error({ message: 'The provided input is invalid. It should be an object, not an array.' }); + error({ + message: withSchemaHint( + 'The provided input is invalid. It should be an object, not an array.', + schemaHint, + ), + }); process.exitCode = CommandExitCodes.InvalidInput; return false; } @@ -167,8 +208,10 @@ export async function getInputOverride(cwd: string, inputFlag: string | undefine input = parsed; source = inputFileFlag; } catch { + const message = `Cannot read input file at path "${fullPath}".\n ${(fsError as Error).message}`; + error({ - message: `Cannot read input file at path "${fullPath}".\n ${(fsError as Error).message}`, + message: withSchemaHint(message, fsError instanceof SyntaxError ? schemaHint : undefined), }); process.exitCode = CommandExitCodes.InvalidInput; return false; diff --git a/test/local/lib/resolve-input.test.ts b/test/local/lib/resolve-input.test.ts new file mode 100644 index 000000000..b8d28471c --- /dev/null +++ b/test/local/lib/resolve-input.test.ts @@ -0,0 +1,55 @@ +import process from 'node:process'; + +import { afterEach, describe, expect, it } from 'vitest'; + +import { getInputOverride } from '../../../src/lib/commands/resolve-input.js'; +import { CommandExitCodes } from '../../../src/lib/consts.js'; +import { useConsoleSpy } from '../../__setup__/hooks/useConsoleSpy.js'; + +const SCHEMA_HINT = 'Run "apify actors info apify/hello-world --input" to inspect the Actor input schema.'; + +const { logMessages } = useConsoleSpy(); + +describe('getInputOverride', () => { + afterEach(() => { + process.exitCode = undefined; + }); + + it('appends schema hint to --input file path errors when provided', async () => { + const result = await getInputOverride(process.cwd(), './input.json', undefined, { schemaHint: SCHEMA_HINT }); + + expect(result).toBe(false); + expect(process.exitCode).toBe(CommandExitCodes.InvalidInput); + const stderr = logMessages.error.join('\n'); + expect(stderr).toContain('Use the "--input-file=" flag instead'); + expect(stderr).toContain(SCHEMA_HINT); + }); + + it('keeps --input file path errors unchanged when schema hint is omitted', async () => { + const result = await getInputOverride(process.cwd(), './input.json', undefined); + + expect(result).toBe(false); + expect(process.exitCode).toBe(CommandExitCodes.InvalidInput); + expect(logMessages.error.join('\n')).not.toContain('apify actors info'); + }); + + it('appends schema hint to malformed inline JSON errors', async () => { + const result = await getInputOverride(process.cwd(), '{"url":', undefined, { schemaHint: SCHEMA_HINT }); + + expect(result).toBe(false); + expect(process.exitCode).toBe(CommandExitCodes.InvalidInput); + const stderr = logMessages.error.join('\n'); + expect(stderr).toContain('Cannot parse JSON input.'); + expect(stderr).toContain(SCHEMA_HINT); + }); + + it('appends schema hint to array input errors', async () => { + const result = await getInputOverride(process.cwd(), '[]', undefined, { schemaHint: SCHEMA_HINT }); + + expect(result).toBe(false); + expect(process.exitCode).toBe(CommandExitCodes.InvalidInput); + const stderr = logMessages.error.join('\n'); + expect(stderr).toContain('It should be an object, not an array.'); + expect(stderr).toContain(SCHEMA_HINT); + }); +}); From e93904a3b7992bb01226d4c155e10551aa92d87e Mon Sep 17 00:00:00 2001 From: Patrik Braborec Date: Wed, 24 Jun 2026 14:37:19 +0200 Subject: [PATCH 2/3] Update src/commands/actors/call.ts Co-authored-by: Edyta <142720610+szaganek@users.noreply.github.com> --- src/commands/actors/call.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/actors/call.ts b/src/commands/actors/call.ts index 98279f2f9..9b8b3544e 100644 --- a/src/commands/actors/call.ts +++ b/src/commands/actors/call.ts @@ -65,7 +65,7 @@ export class ActorsCallCommand extends ApifyCommand { input: Flags.string({ char: 'i', description: - 'Optional inline JSON object input for the Actor. Wrap the JSON in quotes to avoid shell parsing issues. For JSON files, use --input-file.', + 'Optional inline JSON object input for the Actor. To avoid shell parsing issues, wrap the JSON in quotes. For JSON files, use --input-file.', required: false, stdin: StdinMode.Stringified, exclusive: ['input-file'], From 310865b9ed6b4c0ec9d08c6c4690295eda1fc190 Mon Sep 17 00:00:00 2001 From: Patrik Braborec Date: Wed, 24 Jun 2026 14:37:38 +0200 Subject: [PATCH 3/3] Update src/commands/actors/call.ts Co-authored-by: Edyta <142720610+szaganek@users.noreply.github.com> --- src/commands/actors/call.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/actors/call.ts b/src/commands/actors/call.ts index 9b8b3544e..14d31568e 100644 --- a/src/commands/actors/call.ts +++ b/src/commands/actors/call.ts @@ -31,7 +31,7 @@ export class ActorsCallCommand extends ApifyCommand { static override description = 'Executes Actor remotely using your authenticated account.\n' + 'Reads input from local key-value store by default.\n' + - 'Inspect the input schema first with "apify actors info --input".'; + 'To inspect the input schema before creating a JSON input, use "apify actors info --input".'; static override group = 'Apify Console';