Skip to content

feat: push monorepo Actors by uploading the repository root with actorPath - #1505

Draft
DaveHanns wants to merge 4 commits into
feat/push-validate-actor-json-pathsfrom
feat/push-monorepo-upload
Draft

DaveHanns wants to merge 4 commits into
feat/push-validate-actor-json-pathsfrom
feat/push-monorepo-upload

Conversation

@DaveHanns

@DaveHanns DaveHanns commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Summary

apify push of a monorepo Actor (one whose dockerContextDir lies above its folder) now uploads the Git repository root and tells the platform where the Actor lies in it through the actorPath version field, instead of refusing with exit code 255. Both Actors of apify/actor-monorepo-example can then build from a push, as they do from a Git source.

Closes #595 (stage 2). Part of apify/apify-core#28685; needs apify/apify-core#31473 on the platform.

Stacked on the path validation PR (#1503), which sits on the preflight PR (#1502).

Changes

  • In context mode the upload root is the resolver's sourceRoot and the file list comes from getContextFilePaths (Git clone semantics, / separators); outside Git it is the Docker context root with the ordinary list, so .actorignore applies there.
  • The actor.json path check runs with that upload root and the Docker context, so a dockerfile outside the context exits 5 before any platform call.
  • Under MAX_MULTIFILE_BYTES the version gets SOURCE_FILES with names relative to the upload root; otherwise the existing zip upload to the actor-<id>-source store and TARBALL with the (signed) tarballUrl. Every version create or update payload of such a push, including the inline version of a new Actor, carries actorPath; ordinary pushes send no such key.
  • The temporary zip now goes to a unique file under os.tmpdir() for every push that zips, not temp_file.zip in the working directory, and is removed in a finally.
  • The "newer changes on the platform" check scans the upload root and, in context mode, also runs on the zip path (as the issue's design notes ask).
  • A one-line warning on stderr when the repository has uncommitted changes.
  • The platform's multi-file and zip sources carry files only, so a symlink to a file goes as that file, a symlink to a directory inside the repository as copies of the uploaded files under its target (as the ordinary push's file walk does), and a dangling link is left out. Before this, a tracked directory symlink crashed the push with a raw EISDIR or ENOENT error.
  • Skew guard: a 400 whose message names actorPath prints the stage 1 refusal and exits 255; an Actor this push created is deleted first, and a failing delete is reported as a warning without masking the refusal.

Tests

  • test/local/commands/push-monorepo.test.ts (mocked client, no network): source files upload with repository-relative names and actorPath, --json envelope, the zip path (store record with application/zip, TARBALL with a signed tarballUrl, no temp_file.zip left in the working directory), the uncommitted-changes warning, the 400 skew case (refusal, exit 255, clean stdout with --json, deletion of a just-created Actor, a failing delete, a rejected Actor create, other 400s passed through), the no-Git fallback with .actorignore, a directory symlink and a dangling link, a dockerfile outside the Docker context, and the two cases kept from stage 1.
  • pnpm run lint, pnpm run format, pnpm run build, and pnpm run test:local pass, except the two [python] suites (python_support.test.ts, works-with-spaces-in-path-to-actor.test.ts), which fail the same way on the unchanged base branch because of the local Python toolchain.
  • Not run: test:api, which needs a platform token, and the [api] case the issue asks for (see below). Run with Node.js 24.18.0; .nvmrc pins 24.21.0, which is not installed on this machine.

Notes for reviewers

  • The platform side is the schema and build projection change (apify/apify-core#31473) and the worker loader change. Until both are deployed, the skew guard turns the push into the stage 1 refusal, so this PR is safe to merge before them but useless to users until then.
  • The guard matches on the field name in the API error message (core's schema error reads "actorPath is not allowed by the schema"), with the same duck-typed ApifyApiError cast the other commands use.
  • The command description did not change, so docs/ is untouched (pnpm run update-docs produces no diff).
  • Symlinks: getContextFilePaths keeps them as a Git clone does, which the runtime's tar upload can carry but SOURCE_FILES and the zip cannot, so the push resolves them as described under Changes. A link whose target lies outside the repository is left out too, as a clone could not resolve it.
  • Follow-up: the token-gated [api] case in test/api/commands/push.test.ts pushing a two-Actor fixture, once the platform change is deployed.

🤖 Generated with Claude Code

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants