From 2b5ae3a3fa5ecca42c1b049e8cddab71dfa1bf8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20Sol=C3=A1r?= Date: Tue, 7 Jul 2026 21:21:25 +0000 Subject: [PATCH 01/12] refactor(cli): derive TerminalStatus from ACTOR_JOB_TERMINAL_STATUSES Resolves https://github.com/apify/apify-cli/pull/1168#discussion_r3477438476 --- src/lib/commands/agent-output.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/commands/agent-output.ts b/src/lib/commands/agent-output.ts index b10b82329..21f9f3ef6 100644 --- a/src/lib/commands/agent-output.ts +++ b/src/lib/commands/agent-output.ts @@ -5,7 +5,7 @@ import { ACTOR_JOB_TERMINAL_STATUSES } from '@apify/consts'; import { CommandExitCodes } from '../consts.js'; -export type TerminalStatus = 'SUCCEEDED' | 'FAILED' | 'ABORTED' | 'TIMED-OUT'; +export type TerminalStatus = (typeof ACTOR_JOB_TERMINAL_STATUSES)[number]; const TERMINAL_STATUSES = new Set(ACTOR_JOB_TERMINAL_STATUSES as readonly string[]); From f21d0ab497bb877297a983185009067711a3705e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20Sol=C3=A1r?= Date: Tue, 7 Jul 2026 21:34:42 +0000 Subject: [PATCH 02/12] refactor(cli): replace terminal-status Set with includes check Resolves https://github.com/apify/apify-cli/pull/1168#discussion_r3477438553 --- src/lib/commands/agent-output.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/commands/agent-output.ts b/src/lib/commands/agent-output.ts index 21f9f3ef6..cce4d18b6 100644 --- a/src/lib/commands/agent-output.ts +++ b/src/lib/commands/agent-output.ts @@ -7,10 +7,8 @@ import { CommandExitCodes } from '../consts.js'; export type TerminalStatus = (typeof ACTOR_JOB_TERMINAL_STATUSES)[number]; -const TERMINAL_STATUSES = new Set(ACTOR_JOB_TERMINAL_STATUSES as readonly string[]); - export function isTerminalStatus(status: string | undefined): status is TerminalStatus { - return !!status && TERMINAL_STATUSES.has(status); + return !!status && (ACTOR_JOB_TERMINAL_STATUSES as readonly string[]).includes(status); } export function exitCodeForJobStatus(status: string | undefined, kind: 'build' | 'run'): number { From 3124ca070a0414fce3571e8e3acbb093f2aea91c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20Sol=C3=A1r?= Date: Tue, 7 Jul 2026 21:36:59 +0000 Subject: [PATCH 03/12] refactor(cli): use ACTOR_JOB_STATUSES consts in exitCodeForJobStatus Resolves https://github.com/apify/apify-cli/pull/1168#discussion_r3477177982 --- src/lib/commands/agent-output.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/commands/agent-output.ts b/src/lib/commands/agent-output.ts index cce4d18b6..36e678dad 100644 --- a/src/lib/commands/agent-output.ts +++ b/src/lib/commands/agent-output.ts @@ -1,7 +1,7 @@ import type { ActorRun, ApifyClient, Build } from 'apify-client'; import chalk from 'chalk'; -import { ACTOR_JOB_TERMINAL_STATUSES } from '@apify/consts'; +import { ACTOR_JOB_STATUSES, ACTOR_JOB_TERMINAL_STATUSES } from '@apify/consts'; import { CommandExitCodes } from '../consts.js'; @@ -13,13 +13,13 @@ export function isTerminalStatus(status: string | undefined): status is Terminal export function exitCodeForJobStatus(status: string | undefined, kind: 'build' | 'run'): number { switch (status) { - case 'SUCCEEDED': + case ACTOR_JOB_STATUSES.SUCCEEDED: return 0; - case 'TIMED-OUT': - case 'TIMING-OUT': + case ACTOR_JOB_STATUSES.TIMED_OUT: + case ACTOR_JOB_STATUSES.TIMING_OUT: return kind === 'build' ? CommandExitCodes.BuildTimedOut : CommandExitCodes.RunTimedOut; - case 'ABORTED': - case 'ABORTING': + case ACTOR_JOB_STATUSES.ABORTED: + case ACTOR_JOB_STATUSES.ABORTING: return kind === 'build' ? CommandExitCodes.BuildAborted : CommandExitCodes.RunAborted; default: return kind === 'build' ? CommandExitCodes.BuildFailed : CommandExitCodes.RunFailed; From a71de073cd2812b0f3966b66def3a738187814c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20Sol=C3=A1r?= Date: Tue, 7 Jul 2026 21:50:48 +0000 Subject: [PATCH 04/12] refactor(cli): type job status and kind via JobStatus/JobType from consts Derive JobStatus and JobType from @apify/consts and use them across agent-output.ts and useAbortJobOnSignal instead of stringly-typed parameters, updating all call sites to ACTOR_JOB_TYPES. Resolves https://github.com/apify/apify-cli/pull/1168#discussion_r3477438642 --- src/commands/actors/push.ts | 3 ++- src/commands/builds/create.ts | 8 +++++--- src/commands/builds/wait.ts | 6 ++++-- src/commands/runs/wait.ts | 6 ++++-- src/lib/commands/agent-output.ts | 30 ++++++++++++++++------------ src/lib/commands/run-on-cloud.ts | 4 ++-- src/lib/hooks/useAbortJobOnSignal.ts | 14 +++++++------ 7 files changed, 42 insertions(+), 29 deletions(-) diff --git a/src/commands/actors/push.ts b/src/commands/actors/push.ts index 683462834..a9e7ee768 100644 --- a/src/commands/actors/push.ts +++ b/src/commands/actors/push.ts @@ -6,6 +6,7 @@ import type { Actor, ActorCollectionCreateOptions, ActorDefaultRunOptions } from import open from 'open'; import { fetchManifest } from '@apify/actor-templates'; +import { ACTOR_JOB_TYPES } from '@apify/consts'; import { ACTOR_JOB_STATUSES, ACTOR_JOB_TERMINAL_STATUSES, @@ -451,7 +452,7 @@ Skipping push. Use --force to override.`, // build doesn't keep running after the user gives up waiting. using _signalHandler = useAbortJobOnSignal({ apifyClient, - kind: 'build', + kind: ACTOR_JOB_TYPES.BUILD, jobId: build.id, }); diff --git a/src/commands/builds/create.ts b/src/commands/builds/create.ts index 7e386b6ce..5be3fc232 100644 --- a/src/commands/builds/create.ts +++ b/src/commands/builds/create.ts @@ -2,6 +2,8 @@ import process from 'node:process'; import chalk from 'chalk'; +import { ACTOR_JOB_TYPES } from '@apify/consts'; + import { ApifyCommand } from '../../lib/command-framework/apify-command.js'; import { Args } from '../../lib/command-framework/args.js'; import { Flags } from '../../lib/command-framework/flags.js'; @@ -157,7 +159,7 @@ export class BuildsCreateCommand extends ApifyCommand { const { job, timedOutWaiting } = await waitForTerminalStatus({ apifyClient, jobId: buildId, - kind: 'build', + kind: ACTOR_JOB_TYPES.BUILD, maxWaitMillis: timeout ? timeout * 1000 : undefined, pollIntervalMillis: pollInterval ? pollInterval * 1000 : undefined, }); @@ -73,7 +75,7 @@ export class BuildsWaitCommand extends ApifyCommand { const url = consoleBuildUrl(build.actId, build.buildNumber); const ok = build.status === 'SUCCEEDED'; - const exitCode = exitCodeForWaitResult({ job, timedOutWaiting }, 'build'); + const exitCode = exitCodeForWaitResult({ job, timedOutWaiting }, ACTOR_JOB_TYPES.BUILD); const giveUpMessage = `Gave up waiting after ${timeout}s; build is still ${build.status}`; let logTail: string[] = []; diff --git a/src/commands/runs/wait.ts b/src/commands/runs/wait.ts index 92e65547e..c05f1228e 100644 --- a/src/commands/runs/wait.ts +++ b/src/commands/runs/wait.ts @@ -2,6 +2,8 @@ import process from 'node:process'; import type { ActorRun } from 'apify-client'; +import { ACTOR_JOB_TYPES } from '@apify/consts'; + import { ApifyCommand } from '../../lib/command-framework/apify-command.js'; import { Args } from '../../lib/command-framework/args.js'; import { Flags } from '../../lib/command-framework/flags.js'; @@ -70,7 +72,7 @@ export class RunsWaitCommand extends ApifyCommand { const { job, timedOutWaiting } = await waitForTerminalStatus({ apifyClient, jobId: runId, - kind: 'run', + kind: ACTOR_JOB_TYPES.RUN, maxWaitMillis: timeout ? timeout * 1000 : undefined, pollIntervalMillis: pollInterval ? pollInterval * 1000 : undefined, }); @@ -78,7 +80,7 @@ export class RunsWaitCommand extends ApifyCommand { const url = consoleRunUrl(run.actId, run.id); const ok = run.status === 'SUCCEEDED'; - const exitCode = exitCodeForWaitResult({ job, timedOutWaiting }, 'run'); + const exitCode = exitCodeForWaitResult({ job, timedOutWaiting }, ACTOR_JOB_TYPES.RUN); const giveUpMessage = `Gave up waiting after ${timeout}s; run is still ${run.status}`; let logTail: string[] = []; diff --git a/src/lib/commands/agent-output.ts b/src/lib/commands/agent-output.ts index 36e678dad..efedf820c 100644 --- a/src/lib/commands/agent-output.ts +++ b/src/lib/commands/agent-output.ts @@ -1,32 +1,36 @@ import type { ActorRun, ApifyClient, Build } from 'apify-client'; import chalk from 'chalk'; -import { ACTOR_JOB_STATUSES, ACTOR_JOB_TERMINAL_STATUSES } from '@apify/consts'; +import { ACTOR_JOB_STATUSES, ACTOR_JOB_TERMINAL_STATUSES, ACTOR_JOB_TYPES } from '@apify/consts'; import { CommandExitCodes } from '../consts.js'; export type TerminalStatus = (typeof ACTOR_JOB_TERMINAL_STATUSES)[number]; -export function isTerminalStatus(status: string | undefined): status is TerminalStatus { +export type JobStatus = (typeof ACTOR_JOB_STATUSES)[keyof typeof ACTOR_JOB_STATUSES]; + +export type JobType = (typeof ACTOR_JOB_TYPES)[keyof typeof ACTOR_JOB_TYPES]; + +export function isTerminalStatus(status: JobStatus | undefined): status is TerminalStatus { return !!status && (ACTOR_JOB_TERMINAL_STATUSES as readonly string[]).includes(status); } -export function exitCodeForJobStatus(status: string | undefined, kind: 'build' | 'run'): number { +export function exitCodeForJobStatus(status: JobStatus | undefined, kind: JobType): number { switch (status) { case ACTOR_JOB_STATUSES.SUCCEEDED: return 0; case ACTOR_JOB_STATUSES.TIMED_OUT: case ACTOR_JOB_STATUSES.TIMING_OUT: - return kind === 'build' ? CommandExitCodes.BuildTimedOut : CommandExitCodes.RunTimedOut; + return kind === ACTOR_JOB_TYPES.BUILD ? CommandExitCodes.BuildTimedOut : CommandExitCodes.RunTimedOut; case ACTOR_JOB_STATUSES.ABORTED: case ACTOR_JOB_STATUSES.ABORTING: - return kind === 'build' ? CommandExitCodes.BuildAborted : CommandExitCodes.RunAborted; + return kind === ACTOR_JOB_TYPES.BUILD ? CommandExitCodes.BuildAborted : CommandExitCodes.RunAborted; default: - return kind === 'build' ? CommandExitCodes.BuildFailed : CommandExitCodes.RunFailed; + return kind === ACTOR_JOB_TYPES.BUILD ? CommandExitCodes.BuildFailed : CommandExitCodes.RunFailed; } } -export function exitCodeForWaitResult(result: WaitForJobResult, kind: 'build' | 'run'): number { +export function exitCodeForWaitResult(result: WaitForJobResult, kind: JobType): number { // A client-side wait give-up is not a platform timeout, so report it with a distinct exit // code rather than mislabelling the still-running job as having timed out on the platform. return result.timedOutWaiting ? CommandExitCodes.WaitTimedOut : exitCodeForJobStatus(result.job.status, kind); @@ -35,7 +39,7 @@ export function exitCodeForWaitResult(result: WaitForJobResult, kind: 'build' | export interface WaitForJobOptions { apifyClient: ApifyClient; jobId: string; - kind: 'build' | 'run'; + kind: JobType; /** Poll interval in milliseconds. Defaults to 2000. */ pollIntervalMillis?: number; /** Maximum time to wait before giving up. Defaults to no limit. */ @@ -58,12 +62,12 @@ export async function waitForTerminalStatus(options: WaitForJobOptions): Promise while (true) { const job = - kind === 'build' + kind === ACTOR_JOB_TYPES.BUILD ? ((await apifyClient.build(jobId).get()) as Build | undefined) : ((await apifyClient.run(jobId).get()) as ActorRun | undefined); if (!job) { - throw new Error(`${kind === 'build' ? 'Build' : 'Run'} with ID "${jobId}" was not found.`); + throw new Error(`${kind === ACTOR_JOB_TYPES.BUILD ? 'Build' : 'Run'} with ID "${jobId}" was not found.`); } if (isTerminalStatus(job.status)) { @@ -105,9 +109,9 @@ export function consoleDatasetUrl(datasetId: string): string { return `https://console.apify.com/storage/datasets/${datasetId}`; } -function statusColor(status: string): string { - if (status === 'SUCCEEDED') return chalk.green(status); - if (status === 'RUNNING' || status === 'READY') return chalk.blue(status); +function statusColor(status: JobStatus): string { + if (status === ACTOR_JOB_STATUSES.SUCCEEDED) return chalk.green(status); + if (status === ACTOR_JOB_STATUSES.RUNNING || status === ACTOR_JOB_STATUSES.READY) return chalk.blue(status); return chalk.red(status); } diff --git a/src/lib/commands/run-on-cloud.ts b/src/lib/commands/run-on-cloud.ts index 8c421cb99..74ec6164e 100644 --- a/src/lib/commands/run-on-cloud.ts +++ b/src/lib/commands/run-on-cloud.ts @@ -3,7 +3,7 @@ import process from 'node:process'; import type { ActorRun, ApifyClient, TaskStartOptions } from 'apify-client'; import chalk from 'chalk'; -import { ACTOR_JOB_STATUSES } from '@apify/consts'; +import { ACTOR_JOB_STATUSES, ACTOR_JOB_TYPES } from '@apify/consts'; import { Flags } from '../command-framework/flags.js'; import { CommandExitCodes } from '../consts.js'; @@ -117,7 +117,7 @@ export async function* runActorOrTaskOnCloud(apifyClient: ApifyClient, options: // terminated by the consumer (e.g. `break` out of `for await`). using _signalHandler = useAbortJobOnSignal({ apifyClient, - kind: 'run', + kind: ACTOR_JOB_TYPES.RUN, jobId: run.id, runType: type, silent, diff --git a/src/lib/hooks/useAbortJobOnSignal.ts b/src/lib/hooks/useAbortJobOnSignal.ts index ff02872ce..8255e5483 100644 --- a/src/lib/hooks/useAbortJobOnSignal.ts +++ b/src/lib/hooks/useAbortJobOnSignal.ts @@ -1,6 +1,8 @@ import type { ApifyClient } from 'apify-client'; import chalk from 'chalk'; +import { ACTOR_JOB_TYPES } from '@apify/consts'; + import { INTERRUPT_SIGNALS } from '../consts.js'; import { error, info } from '../outputs.js'; import { useSignalHandler } from './useSignalHandler.js'; @@ -13,13 +15,13 @@ export type UseAbortJobOnSignalInput = { } & ( | { /** Abort an Actor build. Builds have no graceful/force distinction. */ - kind: 'build'; + kind: typeof ACTOR_JOB_TYPES.BUILD; /** ID of the build to abort. */ jobId: string; } | { /** Abort an Actor or Task run. Runs escalate: graceful on the first signal, forced on the second. */ - kind: 'run'; + kind: typeof ACTOR_JOB_TYPES.RUN; /** ID of the run to abort. */ jobId: string; /** Used purely for the user-visible status line (e.g. "aborting actor run ..."). */ @@ -36,9 +38,9 @@ export type UseAbortJobOnSignalInput = { * Repeat signals never terminate the CLI while an abort is in flight — the * listener stays registered for the lifetime of the `using` binding: * - * - For `kind: 'build'`, the first signal issues the abort and subsequent + * - For builds, the first signal issues the abort and subsequent * signals are silent no-ops. The build-abort API has no "gracefully" knob. - * - For `kind: 'run'`, the first signal issues `abort({ gracefully: true })` + * - For runs, the first signal issues `abort({ gracefully: true })` * with a hint that pressing Ctrl+C again forces an immediate abort. The * second signal issues `abort({ gracefully: false })`. Third and later * signals are silent no-ops. @@ -48,7 +50,7 @@ export type UseAbortJobOnSignalInput = { * { * using _signalHandler = useAbortJobOnSignal({ * apifyClient: client, - * kind: 'build', + * kind: ACTOR_JOB_TYPES.BUILD, * jobId: build.id, * }); * @@ -67,7 +69,7 @@ export function useAbortJobOnSignal(input: UseAbortJobOnSignalInput): Disposable handler: async (signal) => { abortAttempt += 1; - if (input.kind === 'build') { + if (input.kind === ACTOR_JOB_TYPES.BUILD) { if (abortAttempt > 1) { return; } From 5f7a180a2b2186c84c9d28ccc93dc88ce22ad09a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20Sol=C3=A1r?= Date: Wed, 8 Jul 2026 07:35:56 +0000 Subject: [PATCH 05/12] test(cli): add unit tests for agent-output helpers Covers waitForTerminalStatus polling/timeout/not-found behavior and the isTerminalStatus/exitCodeForJobStatus/exitCodeForWaitResult tables. Resolves https://github.com/apify/apify-cli/pull/1168#discussion_r3477177976 --- test/local/lib/agent-output.test.ts | 102 ++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 test/local/lib/agent-output.test.ts diff --git a/test/local/lib/agent-output.test.ts b/test/local/lib/agent-output.test.ts new file mode 100644 index 000000000..d344207ae --- /dev/null +++ b/test/local/lib/agent-output.test.ts @@ -0,0 +1,102 @@ +import type { ActorRun, ApifyClient, Build } from 'apify-client'; + +import { ACTOR_JOB_TYPES } from '@apify/consts'; + +import { + exitCodeForJobStatus, + exitCodeForWaitResult, + isTerminalStatus, + waitForTerminalStatus, +} from '../../../src/lib/commands/agent-output.js'; +import { CommandExitCodes } from '../../../src/lib/consts.js'; + +const makeBuild = (status: Build['status']): Build => ({ id: 'build123', status }) as Build; +const makeRun = (status: ActorRun['status']): ActorRun => ({ id: 'run123', status }) as ActorRun; + +const fakeClient = ({ builds = [], runs = [] }: { builds?: (Build | undefined)[]; runs?: (ActorRun | undefined)[] }) => + ({ + build: () => ({ get: async () => builds.shift() }), + run: () => ({ get: async () => runs.shift() }), + }) as unknown as ApifyClient; + +describe('isTerminalStatus', () => { + it.each(['SUCCEEDED', 'FAILED', 'ABORTED', 'TIMED-OUT'] as const)('returns true for %s', (status) => { + expect(isTerminalStatus(status)).toBe(true); + }); + + it.each(['READY', 'RUNNING', 'TIMING-OUT', 'ABORTING', undefined] as const)('returns false for %s', (status) => { + expect(isTerminalStatus(status)).toBe(false); + }); +}); + +describe('exitCodeForJobStatus', () => { + it.each([ + ['SUCCEEDED', 0, 0], + ['TIMED-OUT', CommandExitCodes.BuildTimedOut, CommandExitCodes.RunTimedOut], + ['TIMING-OUT', CommandExitCodes.BuildTimedOut, CommandExitCodes.RunTimedOut], + ['ABORTED', CommandExitCodes.BuildAborted, CommandExitCodes.RunAborted], + ['ABORTING', CommandExitCodes.BuildAborted, CommandExitCodes.RunAborted], + ['FAILED', CommandExitCodes.BuildFailed, CommandExitCodes.RunFailed], + ['RUNNING', CommandExitCodes.BuildFailed, CommandExitCodes.RunFailed], + ] as const)('maps %s to build=%i run=%i', (status, buildCode, runCode) => { + expect(exitCodeForJobStatus(status, ACTOR_JOB_TYPES.BUILD)).toBe(buildCode); + expect(exitCodeForJobStatus(status, ACTOR_JOB_TYPES.RUN)).toBe(runCode); + }); +}); + +describe('exitCodeForWaitResult', () => { + it('returns WaitTimedOut when the client gave up waiting', () => { + const result = { job: makeRun('RUNNING'), timedOutWaiting: true }; + expect(exitCodeForWaitResult(result, ACTOR_JOB_TYPES.RUN)).toBe(CommandExitCodes.WaitTimedOut); + }); + + it('falls back to the job status exit code otherwise', () => { + const result = { job: makeBuild('SUCCEEDED'), timedOutWaiting: false }; + expect(exitCodeForWaitResult(result, ACTOR_JOB_TYPES.BUILD)).toBe(0); + }); +}); + +describe('waitForTerminalStatus', () => { + it('returns immediately when the job is already terminal', async () => { + const apifyClient = fakeClient({ builds: [makeBuild('SUCCEEDED')] }); + + const result = await waitForTerminalStatus({ apifyClient, jobId: 'build123', kind: ACTOR_JOB_TYPES.BUILD }); + + expect(result).toEqual({ job: makeBuild('SUCCEEDED'), timedOutWaiting: false }); + }); + + it('polls until the job reaches a terminal status', async () => { + const apifyClient = fakeClient({ runs: [makeRun('RUNNING'), makeRun('RUNNING'), makeRun('SUCCEEDED')] }); + + const result = await waitForTerminalStatus({ + apifyClient, + jobId: 'run123', + kind: ACTOR_JOB_TYPES.RUN, + pollIntervalMillis: 1, + }); + + expect(result).toEqual({ job: makeRun('SUCCEEDED'), timedOutWaiting: false }); + }); + + it('gives up with timedOutWaiting when maxWaitMillis elapses first', async () => { + const apifyClient = fakeClient({ runs: Array.from({ length: 100 }, () => makeRun('RUNNING')) }); + + const result = await waitForTerminalStatus({ + apifyClient, + jobId: 'run123', + kind: ACTOR_JOB_TYPES.RUN, + pollIntervalMillis: 1, + maxWaitMillis: 20, + }); + + expect(result).toEqual({ job: makeRun('RUNNING'), timedOutWaiting: true }); + }); + + it('throws when the job does not exist', async () => { + const apifyClient = fakeClient({ builds: [undefined] }); + + await expect(waitForTerminalStatus({ apifyClient, jobId: 'missing', kind: ACTOR_JOB_TYPES.BUILD })).rejects.toThrow( + 'Build with ID "missing" was not found.', + ); + }); +}); From 3664c003978970f4c1f2c891a8f094f22eb84fdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20Sol=C3=A1r?= Date: Wed, 8 Jul 2026 07:59:50 +0000 Subject: [PATCH 06/12] refactor(cli): drop redundant casts on build/run get results Resolves https://github.com/apify/apify-cli/pull/1168#discussion_r3479445398 --- src/lib/commands/agent-output.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/commands/agent-output.ts b/src/lib/commands/agent-output.ts index efedf820c..2f2653685 100644 --- a/src/lib/commands/agent-output.ts +++ b/src/lib/commands/agent-output.ts @@ -62,9 +62,7 @@ export async function waitForTerminalStatus(options: WaitForJobOptions): Promise while (true) { const job = - kind === ACTOR_JOB_TYPES.BUILD - ? ((await apifyClient.build(jobId).get()) as Build | undefined) - : ((await apifyClient.run(jobId).get()) as ActorRun | undefined); + kind === ACTOR_JOB_TYPES.BUILD ? await apifyClient.build(jobId).get() : await apifyClient.run(jobId).get(); if (!job) { throw new Error(`${kind === ACTOR_JOB_TYPES.BUILD ? 'Build' : 'Run'} with ID "${jobId}" was not found.`); From 28f73f05caef9d36b866ece9164c9287fbee7120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20Sol=C3=A1r?= Date: Wed, 8 Jul 2026 08:03:05 +0000 Subject: [PATCH 07/12] refactor(cli): share fetchLogTail between run-result and agent-output fetchRunLogTail keeps its succeeded-run guard and 10-line tail but delegates the fetch/split/slice to the shared fetchLogTail, which now also trims trailing whitespace. Resolves https://github.com/apify/apify-cli/pull/1168#discussion_r3477448340 --- src/lib/commands/agent-output.ts | 5 ++++- src/lib/commands/run-result.ts | 19 ++----------------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/src/lib/commands/agent-output.ts b/src/lib/commands/agent-output.ts index 2f2653685..a8a7f42dd 100644 --- a/src/lib/commands/agent-output.ts +++ b/src/lib/commands/agent-output.ts @@ -88,7 +88,10 @@ export async function fetchLogTail(apifyClient: ApifyClient, jobId: string, maxL const log = await apifyClient.log(jobId).get(); if (!log) return []; - const lines = log.split('\n').filter((line) => line.length > 0); + const lines = log + .split('\n') + .map((line) => line.trimEnd()) + .filter((line) => line.length > 0); return lines.slice(-maxLines); } catch { return []; diff --git a/src/lib/commands/run-result.ts b/src/lib/commands/run-result.ts index 96661c331..9a6b3631b 100644 --- a/src/lib/commands/run-result.ts +++ b/src/lib/commands/run-result.ts @@ -8,6 +8,7 @@ import { ACTOR_JOB_STATUSES } from '@apify/consts'; import { CommandExitCodes } from '../consts.js'; import { simpleLog } from '../outputs.js'; import { printJsonToStdout } from '../utils.js'; +import { fetchLogTail } from './agent-output.js'; /** Which command produced the run, used for labels and the JSON `operation` field. */ export type RunResultOperation = 'call' | 'task-run'; @@ -81,23 +82,7 @@ export async function fetchRunLogTail(apifyClient: ApifyClient, run: ActorRun): return []; } - let log: string | undefined; - - try { - log = await apifyClient.log(run.id).get(); - } catch { - return []; - } - - if (!log) { - return []; - } - - return log - .split('\n') - .map((line) => line.trimEnd()) - .filter((line) => line.length > 0) - .slice(-LOG_TAIL_LINES); + return fetchLogTail(apifyClient, run.id, LOG_TAIL_LINES); } export interface RunResultOptions { From 0dbdf538da12f87f51d91e8d640d9454c3cd0e5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20Sol=C3=A1r?= Date: Wed, 8 Jul 2026 08:06:31 +0000 Subject: [PATCH 08/12] refactor(cli): widen overallStatus to JobStatus, drop as-never casts Resolves https://github.com/apify/apify-cli/pull/1168#discussion_r3477177986 --- src/commands/builds/create.ts | 2 +- src/commands/builds/wait.ts | 2 +- src/commands/runs/wait.ts | 2 +- src/lib/commands/agent-output.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commands/builds/create.ts b/src/commands/builds/create.ts index 5be3fc232..6f71a8e21 100644 --- a/src/commands/builds/create.ts +++ b/src/commands/builds/create.ts @@ -226,7 +226,7 @@ export class BuildsCreateCommand extends ApifyCommand { simpleLog({ message: formatResultSummary({ resultLabel: 'Apify build result', - overallStatus: build.status as never, + overallStatus: build.status, lines, links, errorReason: ok ? undefined : logTail, diff --git a/src/commands/runs/wait.ts b/src/commands/runs/wait.ts index c05f1228e..ee70e4955 100644 --- a/src/commands/runs/wait.ts +++ b/src/commands/runs/wait.ts @@ -133,7 +133,7 @@ export class RunsWaitCommand extends ApifyCommand { simpleLog({ message: formatResultSummary({ resultLabel: 'Apify run result', - overallStatus: run.status as never, + overallStatus: run.status, lines, links, errorReason: ok ? undefined : logTail, diff --git a/src/lib/commands/agent-output.ts b/src/lib/commands/agent-output.ts index a8a7f42dd..e6bae6a13 100644 --- a/src/lib/commands/agent-output.ts +++ b/src/lib/commands/agent-output.ts @@ -118,7 +118,7 @@ function statusColor(status: JobStatus): string { export interface ResultSummaryOptions { resultLabel: string; // e.g. "Apify push result" - overallStatus: 'SUCCEEDED' | 'FAILED' | 'ABORTED' | 'TIMED-OUT' | 'RUNNING'; + overallStatus: JobStatus; lines: { label: string; value: string }[]; links?: { label: string; url: string }[]; errorReason?: string[]; From c257f1658e68821d0571a2b12469c763a03a3038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20Sol=C3=A1r?= Date: Wed, 8 Jul 2026 08:25:42 +0000 Subject: [PATCH 09/12] refactor(cli): consolidate console URL builders in agent-output Add consoleActorUrl and consoleKeyValueStoreUrl, use the builders in start/call/run-result/runs info/builds info instead of inlined URLs, and standardize build URLs on the /builds/ path format. Resolves https://github.com/apify/apify-cli/pull/1168#discussion_r3477177988 --- src/commands/actors/call.ts | 5 +++-- src/commands/actors/push.ts | 2 +- src/commands/actors/start.ts | 4 ++-- src/commands/builds/info.ts | 3 ++- src/commands/runs/info.ts | 7 ++++--- src/lib/commands/agent-output.ts | 10 +++++++++- src/lib/commands/run-result.ts | 26 ++++++-------------------- 7 files changed, 27 insertions(+), 30 deletions(-) diff --git a/src/commands/actors/call.ts b/src/commands/actors/call.ts index e5f31e25d..a5fa6eb12 100644 --- a/src/commands/actors/call.ts +++ b/src/commands/actors/call.ts @@ -15,9 +15,10 @@ import { ACTOR_JOB_STATUSES } from '@apify/consts'; import { ApifyCommand, StdinMode } from '../../lib/command-framework/apify-command.js'; import { Args } from '../../lib/command-framework/args.js'; import { Flags } from '../../lib/command-framework/flags.js'; +import { consoleRunUrl } from '../../lib/commands/agent-output.js'; import { getInputOverride } from '../../lib/commands/resolve-input.js'; import { runActorOrTaskOnCloud, SharedRunOnCloudFlags } from '../../lib/commands/run-on-cloud.js'; -import { finalizeRun, runUrl } from '../../lib/commands/run-result.js'; +import { finalizeRun } from '../../lib/commands/run-result.js'; import { CommandExitCodes, LOCAL_CONFIG_PATH } from '../../lib/consts.js'; import { error, simpleLog } from '../../lib/outputs.js'; import { getLocalConfig, getLocalUserInfo, getLoggedClientOrThrow, TimestampFormatter } from '../../lib/utils.js'; @@ -176,7 +177,7 @@ export class ActorsCallCommand extends ApifyCommand { // A *lot* is copied from `runs info` if (!this.flags.silent) { - const url = runUrl(actorId, yieldedRun.id); + const url = consoleRunUrl(actorId, yieldedRun.id); const message: string[] = [`${chalk.yellow('Started')}: ${TimestampFormatter.display(yieldedRun.startedAt)}`]; diff --git a/src/commands/actors/push.ts b/src/commands/actors/push.ts index a9e7ee768..8b2cecfea 100644 --- a/src/commands/actors/push.ts +++ b/src/commands/actors/push.ts @@ -508,7 +508,7 @@ Skipping push. Use --force to override.`, const outcome = resolvePushOutcome(buildStatus); const actorUrl = `https://console.apify.com${redirectUrlPart}/actors/${build.actId}`; - const buildUrl = `${actorUrl}#/builds/${build.buildNumber}`; + const buildUrl = `${actorUrl}/builds/${build.buildNumber}`; // Surface the tail of the build log as the failure reason. Best-effort: // the build status already conveys the outcome if the log can't be read. diff --git a/src/commands/actors/start.ts b/src/commands/actors/start.ts index 1ddf99b64..30adcadb3 100644 --- a/src/commands/actors/start.ts +++ b/src/commands/actors/start.ts @@ -6,7 +6,7 @@ import chalk from 'chalk'; import { ApifyCommand, StdinMode } from '../../lib/command-framework/apify-command.js'; import { Args } from '../../lib/command-framework/args.js'; import { Flags } from '../../lib/command-framework/flags.js'; -import { consoleRunUrl } from '../../lib/commands/agent-output.js'; +import { consoleActorUrl, consoleRunUrl } from '../../lib/commands/agent-output.js'; import { getInputOverride } from '../../lib/commands/resolve-input.js'; import { runActorOrTaskOnCloud, SharedRunOnCloudFlags } from '../../lib/commands/run-on-cloud.js'; import { LOCAL_CONFIG_PATH } from '../../lib/consts.js'; @@ -141,7 +141,7 @@ export class ActorsStartCommand extends ApifyCommand waited: false, actor: { id: actorId, - url: `https://console.apify.com/actors/${actorId}`, + url: consoleActorUrl(actorId), }, run: { id: run.id, diff --git a/src/commands/builds/info.ts b/src/commands/builds/info.ts index 7153f1c49..389e2aa5c 100644 --- a/src/commands/builds/info.ts +++ b/src/commands/builds/info.ts @@ -2,6 +2,7 @@ import chalk from 'chalk'; import { ApifyCommand } from '../../lib/command-framework/apify-command.js'; import { Args } from '../../lib/command-framework/args.js'; +import { consoleBuildUrl } from '../../lib/commands/agent-output.js'; import { prettyPrintBytes } from '../../lib/commands/pretty-print-bytes.js'; import { prettyPrintStatus } from '../../lib/commands/pretty-print-status.js'; import { error, simpleLog } from '../../lib/outputs.js'; @@ -103,7 +104,7 @@ export class BuildsInfoCommand extends ApifyCommand { message.push(''); - const url = `https://console.apify.com/actors/${build.actId}/builds/${build.buildNumber}`; + const url = consoleBuildUrl(build.actId, build.buildNumber); message.push(`${chalk.blue('View in Apify Console')}: ${url}`); diff --git a/src/commands/runs/info.ts b/src/commands/runs/info.ts index 7c0118548..9e956b31b 100644 --- a/src/commands/runs/info.ts +++ b/src/commands/runs/info.ts @@ -4,6 +4,7 @@ import chalk from 'chalk'; import { ApifyCommand } from '../../lib/command-framework/apify-command.js'; import { Args } from '../../lib/command-framework/args.js'; import { Flags } from '../../lib/command-framework/flags.js'; +import { consoleDatasetUrl, consoleKeyValueStoreUrl, consoleRunUrl } from '../../lib/commands/agent-output.js'; import { prettyPrintBytes } from '../../lib/commands/pretty-print-bytes.js'; import { prettyPrintStatus } from '../../lib/commands/pretty-print-status.js'; import { CompactMode, ResponsiveTable } from '../../lib/commands/responsive-table.js'; @@ -258,9 +259,9 @@ export class RunsInfoCommand extends ApifyCommand { message.push(''); - const url = `https://console.apify.com/actors/${run.actId}/runs/${run.id}`; - const datasetUrl = `https://console.apify.com/storage/datasets/${run.defaultDatasetId}`; - const keyValueStoreUrl = `https://console.apify.com/storage/key-value-stores/${run.defaultKeyValueStoreId}`; + const url = consoleRunUrl(run.actId, run.id); + const datasetUrl = consoleDatasetUrl(run.defaultDatasetId); + const keyValueStoreUrl = consoleKeyValueStoreUrl(run.defaultKeyValueStoreId); message.push(`${chalk.blue('Export results')}: ${datasetUrl}`); message.push(`${chalk.blue('View saved items')}: ${keyValueStoreUrl}`); diff --git a/src/lib/commands/agent-output.ts b/src/lib/commands/agent-output.ts index e6bae6a13..d7a3f0ee4 100644 --- a/src/lib/commands/agent-output.ts +++ b/src/lib/commands/agent-output.ts @@ -98,18 +98,26 @@ export async function fetchLogTail(apifyClient: ApifyClient, jobId: string, maxL } } +export function consoleActorUrl(actorId: string): string { + return `https://console.apify.com/actors/${actorId}`; +} + export function consoleRunUrl(actorId: string, runId: string): string { return `https://console.apify.com/actors/${actorId}/runs/${runId}`; } export function consoleBuildUrl(actorId: string, buildNumber: string): string { - return `https://console.apify.com/actors/${actorId}#/builds/${buildNumber}`; + return `https://console.apify.com/actors/${actorId}/builds/${buildNumber}`; } export function consoleDatasetUrl(datasetId: string): string { return `https://console.apify.com/storage/datasets/${datasetId}`; } +export function consoleKeyValueStoreUrl(keyValueStoreId: string): string { + return `https://console.apify.com/storage/key-value-stores/${keyValueStoreId}`; +} + function statusColor(status: JobStatus): string { if (status === ACTOR_JOB_STATUSES.SUCCEEDED) return chalk.green(status); if (status === ACTOR_JOB_STATUSES.RUNNING || status === ACTOR_JOB_STATUSES.READY) return chalk.blue(status); diff --git a/src/lib/commands/run-result.ts b/src/lib/commands/run-result.ts index 9a6b3631b..842f8e3fd 100644 --- a/src/lib/commands/run-result.ts +++ b/src/lib/commands/run-result.ts @@ -8,7 +8,7 @@ import { ACTOR_JOB_STATUSES } from '@apify/consts'; import { CommandExitCodes } from '../consts.js'; import { simpleLog } from '../outputs.js'; import { printJsonToStdout } from '../utils.js'; -import { fetchLogTail } from './agent-output.js'; +import { consoleActorUrl, consoleDatasetUrl, consoleRunUrl, fetchLogTail } from './agent-output.js'; /** Which command produced the run, used for labels and the JSON `operation` field. */ export type RunResultOperation = 'call' | 'task-run'; @@ -21,20 +21,6 @@ const OPERATION_LABELS: Record = { /** How many trailing log lines to surface as the failure reason. */ const LOG_TAIL_LINES = 10; -const CONSOLE_BASE_URL = 'https://console.apify.com'; - -function actorUrl(actorId: string) { - return `${CONSOLE_BASE_URL}/actors/${actorId}`; -} - -export function runUrl(actorId: string, runId: string) { - return `${CONSOLE_BASE_URL}/actors/${actorId}/runs/${runId}`; -} - -function datasetUrl(datasetId: string) { - return `${CONSOLE_BASE_URL}/storage/datasets/${datasetId}`; -} - function isSucceeded(run: ActorRun): boolean { return run.status === ACTOR_JOB_STATUSES.SUCCEEDED; } @@ -110,17 +96,17 @@ export function buildRunResultJson({ run, operation, logTail }: RunResultOptions operation, actor: { id: run.actId, - url: actorUrl(run.actId), + url: consoleActorUrl(run.actId), }, run: { id: run.id, status: run.status, - url: runUrl(run.actId, run.id), + url: consoleRunUrl(run.actId, run.id), }, storage: { defaultDatasetId: run.defaultDatasetId, defaultKeyValueStoreId: run.defaultKeyValueStoreId, - datasetUrl: datasetUrl(run.defaultDatasetId), + datasetUrl: consoleDatasetUrl(run.defaultDatasetId), }, exitCode: getRunExitCode(run), }; @@ -158,8 +144,8 @@ export function printRunResultSummary({ run, operation, logTail }: RunResultOpti `${chalk.yellow('Dataset ID')}: ${run.defaultDatasetId}`, `${chalk.yellow('Key-value store ID')}: ${run.defaultKeyValueStoreId}`, '', - `${chalk.blue('Run URL')}: ${runUrl(run.actId, run.id)}`, - `${chalk.blue('Dataset URL')}: ${datasetUrl(run.defaultDatasetId)}`, + `${chalk.blue('Run URL')}: ${consoleRunUrl(run.actId, run.id)}`, + `${chalk.blue('Dataset URL')}: ${consoleDatasetUrl(run.defaultDatasetId)}`, ); if (!ok) { From ac987c14c6b5bf028ed530bfcf57c87594095167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20Sol=C3=A1r?= Date: Wed, 8 Jul 2026 08:40:49 +0000 Subject: [PATCH 10/12] refactor(cli): infer waitForTerminalStatus result type from kind Add a generic overload mapping kind to Build/ActorRun so callers no longer cast the returned job. Resolves https://github.com/apify/apify-cli/pull/1168#discussion_r3477388941 Resolves https://github.com/apify/apify-cli/pull/1168#discussion_r3477400823 Resolves https://github.com/apify/apify-cli/pull/1168#discussion_r3477400908 --- src/commands/builds/create.ts | 2 +- src/commands/builds/wait.ts | 4 +--- src/commands/runs/wait.ts | 4 +--- src/lib/commands/agent-output.ts | 12 ++++++++++-- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/commands/builds/create.ts b/src/commands/builds/create.ts index 6f71a8e21..9857e0e79 100644 --- a/src/commands/builds/create.ts +++ b/src/commands/builds/create.ts @@ -183,7 +183,7 @@ export class BuildsCreateCommand extends ApifyCommand { maxWaitMillis: timeout ? timeout * 1000 : undefined, pollIntervalMillis: pollInterval ? pollInterval * 1000 : undefined, }); - const build = job as Build; + const build = job; const url = consoleBuildUrl(build.actId, build.buildNumber); const ok = build.status === 'SUCCEEDED'; diff --git a/src/commands/runs/wait.ts b/src/commands/runs/wait.ts index ee70e4955..23a19661f 100644 --- a/src/commands/runs/wait.ts +++ b/src/commands/runs/wait.ts @@ -1,7 +1,5 @@ import process from 'node:process'; -import type { ActorRun } from 'apify-client'; - import { ACTOR_JOB_TYPES } from '@apify/consts'; import { ApifyCommand } from '../../lib/command-framework/apify-command.js'; @@ -76,7 +74,7 @@ export class RunsWaitCommand extends ApifyCommand { maxWaitMillis: timeout ? timeout * 1000 : undefined, pollIntervalMillis: pollInterval ? pollInterval * 1000 : undefined, }); - const run = job as ActorRun; + const run = job; const url = consoleRunUrl(run.actId, run.id); const ok = run.status === 'SUCCEEDED'; diff --git a/src/lib/commands/agent-output.ts b/src/lib/commands/agent-output.ts index d7a3f0ee4..1b99e28ce 100644 --- a/src/lib/commands/agent-output.ts +++ b/src/lib/commands/agent-output.ts @@ -46,8 +46,8 @@ export interface WaitForJobOptions { maxWaitMillis?: number; } -export interface WaitForJobResult { - job: Build | ActorRun; +export interface WaitForJobResult { + job: T; /** * True when the wait gave up because `maxWaitMillis` elapsed before the job reached a terminal * status. In that case `job.status` is the real, still-non-terminal platform status (e.g. RUNNING) — @@ -56,6 +56,14 @@ export interface WaitForJobResult { timedOutWaiting: boolean; } +interface JobTypeMap { + BUILD: Build; + RUN: ActorRun; +} + +export async function waitForTerminalStatus( + options: WaitForJobOptions & { kind: K }, +): Promise>; export async function waitForTerminalStatus(options: WaitForJobOptions): Promise { const { apifyClient, jobId, kind, pollIntervalMillis = 2000, maxWaitMillis } = options; const startedAt = Date.now(); From 2e31e4d9a340d9306742fbb53f67b68c72506e67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20Sol=C3=A1r?= Date: Wed, 8 Jul 2026 08:59:48 +0000 Subject: [PATCH 11/12] refactor(cli): drop refreshedBuild intermediate in actors push Resolves https://github.com/apify/apify-cli/pull/1168#discussion_r3477485475 --- src/commands/actors/push.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/commands/actors/push.ts b/src/commands/actors/push.ts index 8b2cecfea..5caec8a60 100644 --- a/src/commands/actors/push.ts +++ b/src/commands/actors/push.ts @@ -2,7 +2,7 @@ import { readFileSync, statSync, unlinkSync } from 'node:fs'; import { join, resolve } from 'node:path'; import process from 'node:process'; -import type { Actor, ActorCollectionCreateOptions, ActorDefaultRunOptions } from 'apify-client'; +import type { Actor, ActorCollectionCreateOptions, ActorDefaultRunOptions, Build } from 'apify-client'; import open from 'open'; import { fetchManifest } from '@apify/actor-templates'; @@ -442,7 +442,7 @@ Skipping push. Use --force to override.`, // share one budget. Without this, a log stream that dies near the cap // would let the poll loop wait another full --wait-for-finish on top. const deadline = waitForFinishMillis === undefined ? Infinity : Date.now() + waitForFinishMillis; - let build = await actorClient.build(version, { + let build: Build | undefined = await actorClient.build(version, { useCache: true, waitForFinish: 2, // NOTE: We need to wait some time to Apify open stream and we can create connection }); @@ -464,13 +464,13 @@ Skipping push. Use --force to override.`, console.error(err); } - const refreshedBuild = await apifyClient.build(build.id).get(); - if (!refreshedBuild) { - error({ message: `Could not fetch build with ID "${build.id}" after deployment.` }); + const buildId = build.id; + build = await apifyClient.build(buildId).get(); + if (!build) { + error({ message: `Could not fetch build with ID "${buildId}" after deployment.` }); process.exitCode = CommandExitCodes.BuildFailed; return; } - build = refreshedBuild; // `outputJobLog` can return before the build is actually terminal (stream // ended early, timeout hit). Poll the remaining budget so the status From f866738a650468ebeded114d60fb19eeb1c2e457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20Sol=C3=A1r?= Date: Wed, 8 Jul 2026 12:54:52 +0200 Subject: [PATCH 12/12] additional cleanup --- src/commands/actors/push.ts | 11 +++-------- src/commands/builds/create.ts | 4 ++-- src/commands/builds/wait.ts | 4 ++-- src/commands/runs/wait.ts | 4 ++-- 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/commands/actors/push.ts b/src/commands/actors/push.ts index 5caec8a60..4908131bc 100644 --- a/src/commands/actors/push.ts +++ b/src/commands/actors/push.ts @@ -6,18 +6,13 @@ import type { Actor, ActorCollectionCreateOptions, ActorDefaultRunOptions, Build import open from 'open'; import { fetchManifest } from '@apify/actor-templates'; -import { ACTOR_JOB_TYPES } from '@apify/consts'; -import { - ACTOR_JOB_STATUSES, - ACTOR_JOB_TERMINAL_STATUSES, - ACTOR_SOURCE_TYPES, - MAX_MULTIFILE_BYTES, -} from '@apify/consts'; +import { ACTOR_JOB_STATUSES, ACTOR_JOB_TYPES, ACTOR_SOURCE_TYPES, MAX_MULTIFILE_BYTES } from '@apify/consts'; import { createHmacSignature } from '@apify/utilities'; import { ApifyCommand } from '../../lib/command-framework/apify-command.js'; import { Args } from '../../lib/command-framework/args.js'; import { Flags } from '../../lib/command-framework/flags.js'; +import { isTerminalStatus } from '../../lib/commands/agent-output.js'; import { CommandExitCodes, DEPRECATED_LOCAL_CONFIG_NAME, LOCAL_CONFIG_PATH } from '../../lib/consts.js'; import { sumFilesSizeInBytes } from '../../lib/files.js'; import { useAbortJobOnSignal } from '../../lib/hooks/useAbortJobOnSignal.js'; @@ -475,7 +470,7 @@ Skipping push. Use --force to override.`, // `outputJobLog` can return before the build is actually terminal (stream // ended early, timeout hit). Poll the remaining budget so the status // branches below see the real outcome. - while (!ACTOR_JOB_TERMINAL_STATUSES.includes(build.status as never) && Date.now() < deadline) { + while (!isTerminalStatus(build.status) && Date.now() < deadline) { await new Promise((resolve) => setTimeout(resolve, 1000)); build = (await apifyClient.build(build.id).get())!; } diff --git a/src/commands/builds/create.ts b/src/commands/builds/create.ts index 9857e0e79..bc988d0d1 100644 --- a/src/commands/builds/create.ts +++ b/src/commands/builds/create.ts @@ -2,7 +2,7 @@ import process from 'node:process'; import chalk from 'chalk'; -import { ACTOR_JOB_TYPES } from '@apify/consts'; +import { ACTOR_JOB_STATUSES, ACTOR_JOB_TYPES } from '@apify/consts'; import { ApifyCommand } from '../../lib/command-framework/apify-command.js'; import { Args } from '../../lib/command-framework/args.js'; @@ -189,7 +189,7 @@ export class BuildsCreateCommand extends ApifyCommand { const build = job; const url = consoleBuildUrl(build.actId, build.buildNumber); - const ok = build.status === 'SUCCEEDED'; + const ok = build.status === ACTOR_JOB_STATUSES.SUCCEEDED; const exitCode = exitCodeForWaitResult({ job, timedOutWaiting }, ACTOR_JOB_TYPES.BUILD); const giveUpMessage = `Gave up waiting after ${timeout}s; build is still ${build.status}`; diff --git a/src/commands/runs/wait.ts b/src/commands/runs/wait.ts index 23a19661f..a910f98cd 100644 --- a/src/commands/runs/wait.ts +++ b/src/commands/runs/wait.ts @@ -1,6 +1,6 @@ import process from 'node:process'; -import { ACTOR_JOB_TYPES } from '@apify/consts'; +import { ACTOR_JOB_STATUSES, ACTOR_JOB_TYPES } from '@apify/consts'; import { ApifyCommand } from '../../lib/command-framework/apify-command.js'; import { Args } from '../../lib/command-framework/args.js'; @@ -77,7 +77,7 @@ export class RunsWaitCommand extends ApifyCommand { const run = job; const url = consoleRunUrl(run.actId, run.id); - const ok = run.status === 'SUCCEEDED'; + const ok = run.status === ACTOR_JOB_STATUSES.SUCCEEDED; const exitCode = exitCodeForWaitResult({ job, timedOutWaiting }, ACTOR_JOB_TYPES.RUN); const giveUpMessage = `Gave up waiting after ${timeout}s; run is still ${run.status}`;