Thanks for your interest in contributing! This guide covers local setup, code style, tests, and the PR process. For a high-level tour of the repo aimed at AI coding assistants, see CLAUDE.md.
- Node.js 22 or higher
- pnpm 10 (enabled via Corepack — do not install pnpm globally with npm)
- Bun ≥ 1.2.5 (optional; only needed if you want to build the standalone bundles locally)
- An Apify account and an API token if you want to run the API test suite
Enable Corepack once per machine:
corepack enablegit clone https://github.com/apify/apify-cli.git
cd apify-cli
pnpm install
pnpm run buildRun the CLI straight from source in dev mode (no global install required):
pnpm run dev:apify --version
pnpm run dev:apify create my-actorpnpm run dev:actor does the same for the in-Actor actor binary.
Use the following environment variables to configure the CLI.
Changes the base URL of Apify Console that the CLI prints and opens. For example, the run, build, dataset and key-value store URLs, or the browser page for the apify login flow.
The default value is https://console.apify.com.
To point the CLI at a local Console instance during development, use:
export APIFY_CONSOLE_URL=http://localhost:3000Note that if APIFY_CONSOLE_URL points at localhost, apify login validates your token against http://localhost:3333. All other commands still call the production API at https://api.apify.com.
The repo uses three tools, wired together via a pre-commit hook (husky + lint-staged):
- Biome — formats JavaScript / TypeScript. Config:
biome.json. - ESLint — lints JavaScript / TypeScript using
@apify/eslint-config. Config:eslint.config.mjs. - Prettier — formats Markdown and YAML. Config: inherited defaults.
Run checks manually:
pnpm run lint # ESLint
pnpm run format # Biome + Prettier (check only)
pnpm run lint:fix # Auto-fix ESLint issues
pnpm run format:fix # Auto-format with Biome + PrettierThe pre-commit hook runs these on staged files automatically. Do not bypass it with --no-verify — if a hook fails, fix the underlying issue and create a new commit.
Run the full local gauntlet before pushing:
pnpm run lintpnpm run formatpnpm run buildpnpm run test:local(andpnpm run test:apiif relevant — see below)
If you added, removed, or changed a command's signature, regenerate the reference docs:
pnpm run update-docsCommit the updated docs/ output alongside your change.
Tests use Vitest. They fall into four categories:
| Script | What it runs | When to run |
|---|---|---|
pnpm run test:local |
Everything except tests named [api] and everything outside test/api/ |
Always, on every change |
pnpm run test:api |
Only tests tagged [api] — hits the real Apify API |
When you touch API-facing code |
pnpm run test:python |
Tests tagged [python] — exercises Python Actor templates |
When you touch Python template / integration code |
pnpm run test:cucumber |
Cucumber features in features/ |
When you touch anything covered by a .feature.md |
pnpm run test:all |
test:local then test:api |
Full pre-release check |
API tests need a token:
TEST_USER_TOKEN=<your-apify-token> pnpm run test:apiUse a dedicated test account — API tests create and destroy Actors, datasets, and other resources.
Shared helpers live in test/__setup__/hooks/. Two of them show up in most new tests:
Isolates Apify authentication state per suite (or per test). Use it in any file that logs in, pushes, pulls, or otherwise touches ~/.apify. By default it recreates the auth setup per test; pass { perTest: false } to share one setup across the suite.
import { useAuthSetup } from "./__setup__/hooks";
useAuthSetup();
// or: useAuthSetup({ perTest: false });API-dependent test cases must have [api] in the test name and live in test/api/. Files outside test/api/ may mix local and [api] tests — the test:local script skips the [api] ones by name.
Creates (and cleans up) a temporary directory, and optionally mocks process.cwd() so commands run as if executed there.
import { useTempPath } from "./__setup__/hooks";
const {
beforeAllCalls,
afterAllCalls,
joinPath,
toggleCwdBetweenFullAndParentPath,
} = useTempPath("my-actor", {
cwd: true,
cwdParent: true,
create: true,
remove: true,
});
const { CreateCommand } = await import("../../src/commands/create.js");Options:
create(defaulttrue) — create the temp directory inbeforeAll.remove(defaulttrue) — remove it inafterAll.cwd(defaultfalse) — mockprocess.cwd()to point at the temp directory.cwdParent(defaultfalse) — start the mocked cwd at the parent of the temp directory.
Returned helpers:
tmpPath— absolute path of the temp directory.joinPath(...segments)—path.joinrooted attmpPath.beforeAllCalls,afterAllCalls— call these from yourbeforeAll/afterAll.toggleCwdBetweenFullAndParentPath()— flip the mocked cwd between the temp dir and its parent.
Important when using cwd: true: in the code you are testing, always import process from 'node:process' (never globalThis.process), and dynamically await import(...) anything that reads cwd after useTempPath runs.
Use testRunCommand from the command framework to invoke CLI commands in tests — it bypasses the entry-point wrapper and gives you the parsed context directly.
- Branch from
master. Name branches descriptively (e.g.fix/push-handles-empty-dir,feat/actors-search). - Write Conventional Commit messages:
feat:,fix:,docs:,chore:,refactor:,test:. The changelog generator (git-cliff) groups entries by prefix, so this matters. - Keep PRs focused. Unrelated cleanup is easier to review in a separate PR.
- Include tests when you fix a bug or add behavior.
- Update
docs/viapnpm run update-docsif you changed any command.
Open PRs against master. Code owners are configured in .github/CODEOWNERS and will be requested automatically.
Releases are fully automated via GitHub Actions — do not bump the version in package.json manually.
- Pre-releases (beta): every push to
mastertriggers.github/workflows/pre_release.yaml, which computes the next beta version, updatesCHANGELOG.md, and publishes to npm under thebetatag. - Stable releases: trigger the Create a release workflow (
.github/workflows/release.yaml) manually from the GitHub Actions UI. It computes the next version (auto / patch / minor / major / custom), updatesCHANGELOG.md, builds standalone bundles for Linux / macOS / Windows (x64 + ARM64), creates a GitHub release with the bundles attached, publishes to npm underlatest, and opens a PR against the Homebrew formula.
Only users with publish access to the apify-cli npm package can trigger the stable release workflow.