diff --git a/src/lib/commands/resolve-input.ts b/src/lib/commands/resolve-input.ts index 0fd04769e..e849e1f95 100644 --- a/src/lib/commands/resolve-input.ts +++ b/src/lib/commands/resolve-input.ts @@ -112,7 +112,7 @@ export async function getInputOverride( inputFlag.startsWith('./') || inputFlag.startsWith('../') || // Home directory access - inputFlag.includes('~') || + inputFlag.startsWith('~') || // Windows-style path access inputFlag.startsWith('.\\') || inputFlag.startsWith('..\\'); diff --git a/test/e2e/commands/actor/run-input.test.ts b/test/e2e/commands/actor/run-input.test.ts index 9a270d4f0..6de91bbc2 100644 --- a/test/e2e/commands/actor/run-input.test.ts +++ b/test/e2e/commands/actor/run-input.test.ts @@ -43,6 +43,17 @@ describe('[e2e] actor run input', () => { expect(run.input).toEqual({ foo: 'bar' }); }); + it('passes JSON string containing ~ to the actor', async () => { + const result = await runCli('apify', ['run', '--input={"standbyActorId":"some-user~some-actor"}'], { + cwd: actor.dir, + }); + + expect(result.exitCode, `stderr: ${result.stderr}`).toBe(0); + const run = await getRunResults(actor.dir); + expect(run.started).toBe(true); + expect(run.input).toEqual({ standbyActorId: 'some-user~some-actor' }); + }); + it('suggests --input-file when given a file path', async () => { const result = await runCli('apify', ['run', '--input=./my-path/file.json'], { cwd: actor.dir }); diff --git a/test/local/lib/resolve-input.test.ts b/test/local/lib/resolve-input.test.ts index 0213865f4..bad4e428f 100644 --- a/test/local/lib/resolve-input.test.ts +++ b/test/local/lib/resolve-input.test.ts @@ -52,4 +52,43 @@ describe('getInputOverride', () => { expect(stderr).toContain('It should be an object, not an array.'); expect(stderr).toContain(SCHEMA_HINT); }); + + it.each([ + ['actor ID separator', '{"standbyActorId":"some-user~some-actor"}', { standbyActorId: 'some-user~some-actor' }], + ['object key', '{"some~key":"value"}', { 'some~key': 'value' }], + [ + 'nested object and array values', + '{"actor":{"id":"some-user~some-actor"},"aliases":["one~two"]}', + { actor: { id: 'some-user~some-actor' }, aliases: ['one~two'] }, + ], + ])('accepts inline JSON containing ~ in %s', async (_description, inputFlag, expectedInput) => { + const result = await getInputOverride(process.cwd(), inputFlag, undefined); + + expect(result).toEqual({ + input: expectedInput, + source: 'input', + }); + expect(process.exitCode).toBeUndefined(); + }); + + it('reports malformed inline JSON containing ~ as a JSON parse error', async () => { + const result = await getInputOverride(process.cwd(), '{"standbyActorId":"some-user~some-actor"', undefined); + + 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).not.toContain('Use the "--input-file=" flag instead'); + }); + + it.each(['~/input.json', '~some-user/input.json'])( + 'still rejects a home-directory path starting with ~: %s', + async (inputFlag) => { + const result = await getInputOverride(process.cwd(), inputFlag, undefined); + + expect(result).toBe(false); + expect(process.exitCode).toBe(CommandExitCodes.InvalidInput); + expect(logMessages.error.join('\n')).toContain('Use the "--input-file=" flag instead'); + }, + ); });