You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
TL;DR — this is experimental idea of mine (POC tried only locally) to have more straightforward way how to test features in PRs. 2 Phases:
Phase 1 (can be standalone) -> use https://pkg.pr.new to build preview version per PR, so review do not need to clone and build by himself and add published url (or install comand) as a comment to PR.
Phase 2 (this depends on Phase 1)-> use webcontainer (or other solution) to start apify-cli in browser with version published by https://pkg.pr.new. It could be standby actor with apify console auth enabled (or public?) -> apify-cli-preview.apify.actor/?pkg=https://pkg.pr.new/apify-cli@<pr-number> some corner cases would be problem to emulate in browser env, but for general check/verify of changes this could be useful.
This is really just experiment and RFC in one, lot of open questions, feel free to criticize.
Screen.Recording.2026-08-27.at.12.02.29.mov
Motivation
Trying a change to apify-cli today means cloning the repo, installing, and linking
the binary. That is a high bar for two groups we care about:
Reviewers — verifying a PR's actual CLI behaviour, not just reading the diff.
Users — reproducing a bug report, or trying a fix before it ships to npm.
Both are solved by the same pair of pieces: a per-commit npm package, and a page
that installs an arbitrary package spec into a browser-based Node environment.
Splitting into two phases because phase 1 is independently useful — it needs no
hosting, no headers, and no licensing decision.
Phase 1 — per-PR preview packages via pkg.pr.new
Publish an installable package for every PR commit, so anyone can try a change
without a local checkout.
Opt-in by label. A preview build is published only while the PR carries a cli-preview label — not for every PR. Reasons:
Most PRs (docs, chores, refactors) do not need a package; publishing for all of
them is noise in the PR thread and wasted CI minutes.
The label is a maintainer action, so it doubles as a human gate before we build
anything from a fork's branch.
Whoever wants a preview asks for it, which keeps the signal meaningful.
This is the same shape vite uses for its preview releases (label-gated, plain pull_request, works for fork contributors).
Scope
One-time setup: install the pkg.pr.new GitHub App
on apify/apify-cli (needs an org admin). No npm token, no repo secrets.
Workflow on pull_request (labeled, synchronize), with the job guarded on
the label being present:
labeled covers "add the label to an existing PR"; synchronize republishes on
each subsequent push while the label stays on. Note labeled fires once per
label added — adding several labels at once starts several runs; the guard
keeps the extras from publishing.
Build, then publish via pnpm exec pkg-pr-new publish — the CLI packs the
tree, it does not build it, so the workflow runs pnpm run build first.
The install-line PR comment comes for free: pkg.pr.new's default
(--comment update) posts one comment per PR and edits it in place on each
push. No comment step to write, and it works on fork PRs because the comment
is posted by the App, not by the workflow's token.
Decide whether removing the label should stop future publishes only (simple) or
also edit the comment to say the preview is stale (nicer). Suggest starting with
the former.
Result — a reviewer labels the PR, then runs one command:
npx https://pkg.pr.new/apify-cli@<pr-number>
(Compact form; @<sha> and @<branch> also work, and the long form is https://pkg.pr.new/apify/apify-cli/apify-cli@<sha>.) npx runs the bin matching
the package name, i.e. the apify entrypoint; a reviewer who wants both apify
and actor on their PATH should npm i -g the same URL instead.
Previews are temporary: pkg.pr.new deletes builds not downloaded for a month, and
everything after six months. Fine for PR review; do not link them from docs.
Acceptance criteria
Adding the cli-preview label to an open PR publishes a build and posts the
install command.
Pushes to a labelled PR republish and update the same comment with the new SHA.
An unlabelled PR publishes nothing.
The cli-preview label exists in the repo with a description saying what it does.
A labelled fork PR publishes and gets the comment (expected to work — see Risks).
Phase 2 — hosted browser sandbox on GitHub Pages
A static page that boots a WebContainer (Node in the
browser, via WASM), installs apify-cli, and drops the visitor into a terminal.
Takes the package spec as a query parameter, so it composes with phase 1:
With no parameter it installs the latest published release — a permanent
"try apify-cli, zero install" link for the docs and the README.
Scope
Static page: boot WebContainer, mount a package.json, run npm install, attach
an xterm terminal to the container shell. (Working prototype exists, ~130 lines,
no build step, no dependencies.)
coi-serviceworker to supply the COOP/COEP headers, since GitHub Pages cannot
set headers. Prior art: webcontainers-ghpages
(a small third-party demo, not a StackBlitz project — but the technique is exactly this).
Pages deploy workflow.
Unsupported-browser message.
Not enabled on forks. The Pages deploy runs only from the base repo:
The deploy workflow triggers on push to the default branch, never on pull_request, and is guarded on the repository so that forks of this repo do
not try to publish to their own Pages site:
if: github.repository == '<org>/<repo>'
No sandbox link is posted on fork PRs. A maintainer who wants to try a fork's
change labels the PR (phase 1), which produces both the package and a usable
sandbox link.
This is a deliberate limitation, not an oversight: it keeps untrusted branches out
of anything that holds a Pages deploy token.
Explicitly out of scope: one deployed page per PR. The ?pkg= parameter makes
it unnecessary — one deploy serves every PR, and there is nothing to clean up when
a PR closes. A page-per-PR variant would need a gh-pages branch (the Pages
artifact deploy replaces the whole site) plus a teardown job on pull_request: closed, and it does not work for fork PRs without pull_request_target.
Acceptance criteria
Page boots and installs the latest release in a supported browser.
?pkg= override installs a pkg.pr.new build from an open PR.
apify --help runs in the terminal.
Unsupported browsers get a clear message instead of a raw npm error.
No deploy is triggered by a fork, and forks of the repo cannot publish.
Licensing question resolved before the page goes public (see Risks).
Verified during prototyping
Recorded so nobody re-derives it:
Safari does not work in practice. StackBlitz lists Safari 16.4+ as supported in beta, but npm install inside the container fails on current Safari (observed on 18.6) with ERR_INVALID_PROTOCOL: Protocol "https:" not supported. Expected "http:" — an
unrelated-looking error that costs an hour to diagnose. Same symptom reported in webcontainer-core#1983,
open with no maintainer response. Full support is Chromium and Firefox. Hence
the unsupported-browser message above.
CORS is not an obstacle, but it is the load-bearing assumption. WebContainer
networking is browser fetch under the hood — no raw TCP, every outbound request
is CORS-bound. All three endpoints the sandbox needs send access-control-allow-origin: * today: registry.npmjs.org, pkg.pr.new, and api.apify.com. If the API ever drops that header, the sandboxed CLI silently
loses the platform.
Buffer npm's output, do not stream it. The install spinner renders as
hundreds of \ | / - lines in xterm and buries real errors.
Risks / open questions
Licensing. StackBlitz requires a license for production use of the
WebContainer API in a commercial, for-profit setting —
their wording covers use "to meet the needs of your customers, prospective
customers, and/or employees", which a public Apify sandbox plausibly is.
Prototypes and POCs are explicitly exempt, so the current prototype is fine.
Needs an answer before a public deploy. Blocks phase 2 only.
Fork PRs — smaller than it looks.pkg-pr-new publish needs no secrets and
never touches the workflow's read-only GITHUB_TOKEN; the PR comment is posted
by the pkg.pr.new App, which holds pull-requests write on the base repo. So the
plain pull_request trigger works for fork PRs — no pull_request_target, no workflow_run follow-up. What remains is GitHub's own gate: first-time
contributors' workflow runs need maintainer approval, and the cli-preview
label is a second deliberate human gate on top. Phase 2 sidesteps forks
entirely by not deploying from them (see above).
Alternatives evaluated, none viable today. OpenWebContainer (npm support
still on the roadmap), Nodepod (MIT + Commons Clause; commercial use restricted),
Nodebox (no sockets to external IPs, so the CLI cannot reach api.apify.com), and
userland.run/nano. Nano runs the real Node 25 RISC-V binary and works, but in
our measurements booted 52× slower and computed 110× slower than host Node, plus
a ~68 MB binary download — unusable for an oclif CLI. Worth re-checking in
~6 months.
Important
TL;DR — this is experimental idea of mine (POC tried only locally) to have more straightforward way how to test features in PRs. 2 Phases:
apify-cli-preview.apify.actor/?pkg=https://pkg.pr.new/apify-cli@<pr-number>some corner cases would be problem to emulate in browser env, but for general check/verify of changes this could be useful.This is really just experiment and RFC in one, lot of open questions, feel free to criticize.
Screen.Recording.2026-08-27.at.12.02.29.mov
Motivation
Trying a change to
apify-clitoday means cloning the repo, installing, and linkingthe binary. That is a high bar for two groups we care about:
Both are solved by the same pair of pieces: a per-commit npm package, and a page
that installs an arbitrary package spec into a browser-based Node environment.
Splitting into two phases because phase 1 is independently useful — it needs no
hosting, no headers, and no licensing decision.
Phase 1 — per-PR preview packages via pkg.pr.new
Publish an installable package for every PR commit, so anyone can try a change
without a local checkout.
Opt-in by label. A preview build is published only while the PR carries a
cli-previewlabel — not for every PR. Reasons:them is noise in the PR thread and wasted CI minutes.
anything from a fork's branch.
This is the same shape vite uses for its preview releases (label-gated, plain
pull_request, works for fork contributors).Scope
on
apify/apify-cli(needs an org admin). No npm token, no repo secrets.pull_request(labeled,synchronize), with the job guarded onthe label being present:
labeledcovers "add the label to an existing PR";synchronizerepublishes oneach subsequent push while the label stays on. Note
labeledfires once perlabel added — adding several labels at once starts several runs; the guard
keeps the extras from publishing.
pnpm exec pkg-pr-new publish— the CLI packs thetree, it does not build it, so the workflow runs
pnpm run buildfirst.(
--comment update) posts one comment per PR and edits it in place on eachpush. No comment step to write, and it works on fork PRs because the comment
is posted by the App, not by the workflow's token.
also edit the comment to say the preview is stale (nicer). Suggest starting with
the former.
Result — a reviewer labels the PR, then runs one command:
(Compact form;
@<sha>and@<branch>also work, and the long form ishttps://pkg.pr.new/apify/apify-cli/apify-cli@<sha>.)npxruns the bin matchingthe package name, i.e. the
apifyentrypoint; a reviewer who wants bothapifyand
actoron their PATH shouldnpm i -gthe same URL instead.Previews are temporary: pkg.pr.new deletes builds not downloaded for a month, and
everything after six months. Fine for PR review; do not link them from docs.
Acceptance criteria
cli-previewlabel to an open PR publishes a build and posts theinstall command.
cli-previewlabel exists in the repo with a description saying what it does.Phase 2 — hosted browser sandbox on GitHub Pages
A static page that boots a WebContainer (Node in the
browser, via WASM), installs
apify-cli, and drops the visitor into a terminal.Takes the package spec as a query parameter, so it composes with phase 1:
With no parameter it installs the latest published release — a permanent
"try apify-cli, zero install" link for the docs and the README.
Scope
package.json, runnpm install, attachan xterm terminal to the container shell. (Working prototype exists, ~130 lines,
no build step, no dependencies.)
coi-serviceworkerto supply the COOP/COEP headers, since GitHub Pages cannotset headers. Prior art: webcontainers-ghpages
(a small third-party demo, not a StackBlitz project — but the technique is exactly this).
Not enabled on forks. The Pages deploy runs only from the base repo:
pull_request, and is guarded on the repository so that forks of this repo donot try to publish to their own Pages site:
change labels the PR (phase 1), which produces both the package and a usable
sandbox link.
This is a deliberate limitation, not an oversight: it keeps untrusted branches out
of anything that holds a Pages deploy token.
Explicitly out of scope: one deployed page per PR. The
?pkg=parameter makesit unnecessary — one deploy serves every PR, and there is nothing to clean up when
a PR closes. A page-per-PR variant would need a
gh-pagesbranch (the Pagesartifact deploy replaces the whole site) plus a teardown job on
pull_request: closed, and it does not work for fork PRs withoutpull_request_target.Acceptance criteria
?pkg=override installs a pkg.pr.new build from an open PR.apify --helpruns in the terminal.Verified during prototyping
Recorded so nobody re-derives it:
supported in beta, but
npm installinside the container fails on current Safari (observed on 18.6) withERR_INVALID_PROTOCOL: Protocol "https:" not supported. Expected "http:"— anunrelated-looking error that costs an hour to diagnose. Same symptom reported in
webcontainer-core#1983,
open with no maintainer response. Full support is Chromium and Firefox. Hence
the unsupported-browser message above.
networking is browser fetch under the hood — no raw TCP, every outbound request
is CORS-bound. All three endpoints the sandbox needs send
access-control-allow-origin: *today:registry.npmjs.org,pkg.pr.new, andapi.apify.com. If the API ever drops that header, the sandboxed CLI silentlyloses the platform.
hundreds of
\ | / -lines in xterm and buries real errors.Risks / open questions
WebContainer API in a commercial, for-profit setting —
their wording covers use "to meet the needs of your customers, prospective
customers, and/or employees", which a public Apify sandbox plausibly is.
Prototypes and POCs are explicitly exempt, so the current prototype is fine.
Needs an answer before a public deploy. Blocks phase 2 only.
pkg-pr-new publishneeds no secrets andnever touches the workflow's read-only
GITHUB_TOKEN; the PR comment is postedby the pkg.pr.new App, which holds pull-requests write on the base repo. So the
plain
pull_requesttrigger works for fork PRs — nopull_request_target, noworkflow_runfollow-up. What remains is GitHub's own gate: first-timecontributors' workflow runs need maintainer approval, and the
cli-previewlabel is a second deliberate human gate on top. Phase 2 sidesteps forks
entirely by not deploying from them (see above).
still on the roadmap), Nodepod (MIT + Commons Clause; commercial use restricted),
Nodebox (no sockets to external IPs, so the CLI cannot reach api.apify.com), and
userland.run/nano. Nano runs the real Node 25 RISC-V binary and works, but in
our measurements booted 52× slower and computed 110× slower than host Node, plus
a ~68 MB binary download — unusable for an oclif CLI. Worth re-checking in
~6 months.
🤖 Generated with Claude Code