Skip to content
Open
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
8 changes: 3 additions & 5 deletions src/commands/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,11 +447,9 @@ export class RunCommand extends ApifyCommand<typeof RunCommand> {
});
}
} catch (err) {
const { stderr } = err as ExecaError;

if (stderr) {
// TODO: maybe throw in helpful tips for debugging issues (missing scripts, trying to start a ts file with old node, etc)
}
Comment on lines -450 to -454

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't remove the old code with the cast but ik we had some ideas to inspect the stderr output and give hints. Idk why we never propagated the exitCode tho 😅

// Propagate the child's failure so the CLI exits non-zero (e.g. for CI/shell chains).
// The command framework only defaults exitCode to 1; preserve the Actor's own code when present.
process.exitCode = (err as ExecaError).exitCode ?? 1;
} finally {
if (storedInputResults) {
if ('tempInputKey' in storedInputResults) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { copyFile } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';

import { testRunCommand } from '../../../../../../src/lib/command-framework/apify-command.js';
import { useTempPath } from '../../../../../__setup__/hooks/useTempPath.js';

const actorName = 'propagates-non-zero-exit-code';

const failingActorMainPath = fileURLToPath(new URL('./sources/failing-main.js', import.meta.url));

const { beforeAllCalls, afterAllCalls, joinPath, toggleCwdBetweenFullAndParentPath } = useTempPath(actorName, {
create: true,
remove: true,
cwd: true,
cwdParent: true,
});

const { CreateCommand } = await import('../../../../../../src/commands/create.js');
const { RunCommand } = await import('../../../../../../src/commands/run.js');

describe('[javascript] propagates the non-zero exit code when the Actor fails', () => {
beforeAll(async () => {
await beforeAllCalls();

await testRunCommand(CreateCommand, {
flags_template: 'project_empty',
args_actorName: actorName,
});
toggleCwdBetweenFullAndParentPath();

await copyFile(failingActorMainPath, joinPath('src', 'main.js'));
});

afterAll(async () => {
await afterAllCalls();
});

it('should set process.exitCode to the Actor exit code', async () => {
// The cwd mock (useTempPath with cwd: true) replaces the `node:process` module, so the
// command writes process.exitCode to that mocked copy. Read it back from the same instance.
const { default: cliProcess } = await import('node:process');
cliProcess.exitCode = 0;

await testRunCommand(RunCommand, { flags_purge: true });

// Matches the `process.exit(10)` in sources/failing-main.js
expect(cliProcess.exitCode).toBe(10);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Fixture for the run exit-code propagation test.
// See https://github.com/apify/apify-cli/issues/1180 and https://github.com/apify/apify-cli/issues/1190
// A failing Actor: `npm start` exits with a non-zero code that the CLI must
// propagate instead of swallowing.
process.exit(10);
Loading