Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/rename-cli-to-playground.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"playground-cli": minor
---

Rename the CLI command from `dot` to `playground`, with `pg` as a short alias. Both `playground` and `pg` invoke the same binary, so `playground init` and `pg init` (and every other subcommand) are interchangeable. The curl installer now symlinks both names onto your PATH and prints a yellow "next step" box showing that either command works. Release artifacts are still published as `dot-<os>-<arch>`; only the installed command names changed. The old `dot` command is no longer installed.
8 changes: 4 additions & 4 deletions .github/workflows/e2e-post-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,19 @@ jobs:
TAG="${{ steps.tag.outputs.tag }}"
# Pin to the just-released version; install.sh resolves "latest"
# by default which can lag behind a just-published release.
# install.sh runs `dot init` at the end; init requires an
# interactive QR scan and will exit non-zero in headless CI —
# install.sh runs `playground init` at the end; init requires
# an interactive QR scan and will exit non-zero in headless CI —
# that is expected. The binary is installed before init runs,
# so we tolerate the init failure and verify the binary below.
curl -fsSL "https://raw.githubusercontent.com/${{ github.repository }}/${{ github.sha }}/install.sh" \
| VERSION="$TAG" bash || true
# Hard-fail here if the binary was not installed — that IS a
# real regression in the install script's download/chmod path.
ls -la "$HOME/.polkadot/bin/dot"
ls -la "$HOME/.polkadot/bin/playground"

- name: Compute installed-binary path
shell: bash
run: echo "DOT_E2E_BINARY=$HOME/.polkadot/bin/dot" >> "$GITHUB_ENV"
run: echo "DOT_E2E_BINARY=$HOME/.polkadot/bin/playground" >> "$GITHUB_ENV"

- name: Smoke test the installed binary
uses: nick-fields/retry@v3
Expand Down
42 changes: 21 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# playground-cli

CLI tooling for Polkadot Playground. Installed as the `dot` command.
CLI tooling for Polkadot Playground. Installed as the `playground` command, with `pg` as a short alias — both invoke the same binary, so `playground init` and `pg init` are interchangeable.

## Quick Start

Expand All @@ -14,11 +14,11 @@ To install a specific version:
curl -fsSL https://raw.githubusercontent.com/paritytech/playground-cli/main/install.sh | VERSION=v0.2.0 bash
```

The installer drops the binary into `~/.polkadot/bin/`, symlinks it at `~/.local/bin/dot`, appends the path to your shell rc, and then runs `dot init` so you can finish setup without a second command.
The installer drops the binary into `~/.polkadot/bin/playground`, symlinks both `playground` and the short `pg` alias into `~/.local/bin/`, appends the path to your shell rc, and then runs `playground init` so you can finish setup without a second command.

## Commands

### `dot init`
### `playground init`

End-to-end first-run setup. Login and toolchain install run **concurrently**; account setup runs **once both have completed successfully**.

Expand All @@ -33,19 +33,19 @@ Flags:

- `-y, --yes` — skip the QR login entirely. Dependencies still install, account setup is skipped (no session).

### `dot update`
### `playground update`

Self-update from the latest GitHub release. Detects your OS/arch, downloads the corresponding `dot-<os>-<arch>` asset, verifies HOME is set, and atomically replaces the running binary (write-to-staging-then-rename so the running process is never served a half-written file).

### `dot build`
### `playground build`

Auto-detects the project's package manager (pnpm / yarn / bun / npm from the lockfile) and runs the `build` npm script. If no `build` script is defined, falls back to a framework invocation (`vite build`, `next build`, `tsc`) based on what's installed.

Flags:

- `--dir <path>` — project directory (defaults to the current working directory).

### `dot deploy`
### `playground deploy`

Builds the project, uploads the output to Bulletin, registers a `.dot` domain via DotNS, and optionally publishes the app to the Playground registry (so it shows up in the user's "my apps" list).

Expand All @@ -57,13 +57,13 @@ Flags:
- `--no-build` — skip the frontend build step and deploy whatever is already in `--buildDir`.
- `--playground` — publish to the playground registry so the app appears under "my apps". Interactive prompt (default: no) if omitted.
- `--private` — publish to the playground with private (owner-only) visibility. Requires `--playground`. Not interactively prompted; pass the flag to opt in.
- `--moddable` / `--no-moddable` — publish the source repo URL alongside the deploy so others can `dot mod` it. Requires `--playground`. Interactive prompt (default: no) if omitted. The CLI reads your existing `origin` and records its URL in the Bulletin metadata; it never creates a repo or pushes for you. The deploy fails with an actionable message if `origin` is unset, points to a private repo, or points to anything other than GitHub (since `dot mod` only fetches from `codeload.github.com`). Set up the repo yourself before re-running: create a public repo on GitHub, then `git remote add origin https://github.com/<user>/<repo>` followed by `git push -u origin main`. (If you happen to have `gh` installed, `gh repo create my-app --public --source=. --push` does both in one shot — `dot` does not require `gh`.)
- `--moddable` / `--no-moddable` — publish the source repo URL alongside the deploy so others can `playground mod` it. Requires `--playground`. Interactive prompt (default: no) if omitted. The CLI reads your existing `origin` and records its URL in the Bulletin metadata; it never creates a repo or pushes for you. The deploy fails with an actionable message if `origin` is unset, points to a private repo, or points to anything other than GitHub (since `playground mod` only fetches from `codeload.github.com`). Set up the repo yourself before re-running: create a public repo on GitHub, then `git remote add origin https://github.com/<user>/<repo>` followed by `git push -u origin main`. (If you happen to have `gh` installed, `gh repo create my-app --public --source=. --push` does both in one shot — `playground` does not require `gh`.)
- `--suri <suri>` — override signer with a dev secret URI (e.g. `//Alice`). Useful for CI.
- `--env <env>` — target environment. Defaults to `paseo-next-v2` (the only one fully wired today). Accepts the bulletin-deploy env IDs (`preview`, `paseo-next`, `paseo-review`, `paseo-next-v2`, `polkadot`, `kusama`) plus the legacy `testnet`/`mainnet` aliases — `testnet` maps to `paseo-next-v2`, `mainnet` to `polkadot`. Any env other than `paseo-next-v2` throws "not supported" until its entry is wired up in `src/config.ts::CONFIGS`.

Passing all four of `--signer`, `--domain`, `--buildDir`, and `--playground` runs in fully non-interactive mode. Any absent flag is filled in by the TUI prompt. `--moddable` and `--private` are independently optional in both modes — their absence means a non-moddable, public deploy.

**Requirement**: the `ipfs` CLI (Kubo) must be on `PATH`. `dot init` installs it; if you skipped init you can install it manually (`brew install ipfs` or follow [docs.ipfs.tech/install](https://docs.ipfs.tech/install/)). This is a temporary requirement while `bulletin-deploy`'s pure-JS merkleizer has a bug that makes the browser fallback unusable.
**Requirement**: the `ipfs` CLI (Kubo) must be on `PATH`. `playground init` installs it; if you skipped init you can install it manually (`brew install ipfs` or follow [docs.ipfs.tech/install](https://docs.ipfs.tech/install/)). This is a temporary requirement while `bulletin-deploy`'s pure-JS merkleizer has a bug that makes the browser fallback unusable.

The publish step is always signed by the user so the registry contract records their address as the app owner — this is what drives the Playground "my apps" view.

Expand All @@ -76,16 +76,16 @@ For fully non-interactive (CI) runs, combine `--signer`, `--domain`, `--buildDir
- `--no-moddable` — explicitly skip source publishing even if `--moddable` would otherwise apply.
- `--private` — publish to the playground with owner-only visibility.

### `dot contract`
### `playground contract`

CDM-backed workflows for contracts:

- `dot contract deploy` builds, deploys, and registers CDM contracts with dot's logged-in signer by default. Pass `--suri //Alice` for local/dev signing.
- `dot contract deploy --features <features>` forwards Cargo feature flags into CDM's build pipeline.
- `dot contract deploy --registry-address <address>` targets a specific CDM registry.
- `dot contract install [libraries...]` uses the CDM install backend with dot's native TUI, then writes `cdm.json` and CDM post-install outputs.
- `playground contract deploy` builds, deploys, and registers CDM contracts with playground's logged-in signer by default. Pass `--suri //Alice` for local/dev signing.
- `playground contract deploy --features <features>` forwards Cargo feature flags into CDM's build pipeline.
- `playground contract deploy --registry-address <address>` targets a specific CDM registry.
- `playground contract install [libraries...]` uses the CDM install backend with playground's native TUI, then writes `cdm.json` and CDM post-install outputs.

### `dot mod`
### `playground mod`

Pull a moddable playground app's source into a fresh local project so you can customise and re-deploy it. The interactive picker only shows apps that opted into moddable at deploy time; non-moddable apps surface a clear "this app is not moddable" error if you target them by domain.

Expand All @@ -98,9 +98,9 @@ Flags:

The local directory name is auto-generated as `<slug>-<6 hex chars>` so repeated mods of the same starter never collide (unlike GitHub forks, which were limited to one per account per repo).

### `dot logout`
### `playground logout`

Sign out of the account paired via `dot init`. Sends a `Disconnected` statement so the paired Polkadot mobile app drops its side of the connection, then clears the local session files under `~/.polkadot-apps/`. If the remote notification fails (statement store unreachable, …), the local files are still cleared and the command surfaces a `partial` status — the mobile app will show a stale pairing until it reconnects. No-op when no session is signed in.
Sign out of the account paired via `playground init`. Sends a `Disconnected` statement so the paired Polkadot mobile app drops its side of the connection, then clears the local session files under `~/.polkadot-apps/`. If the remote notification fails (statement store unreachable, …), the local files are still cleared and the command surfaces a `partial` status — the mobile app will show a stale pairing until it reconnects. No-op when no session is signed in.

## Troubleshooting

Expand All @@ -117,10 +117,10 @@ Telemetry scrubs local home-directory paths and avoids sending raw command argum

### Reporting a memory issue

If `dot deploy` gets killed with `✖ Memory use exceeded 4 GB` (the watchdog's abort) or you see RSS climb unexpectedly, re-run with both of:
If `playground deploy` gets killed with `✖ Memory use exceeded 4 GB` (the watchdog's abort) or you see RSS climb unexpectedly, re-run with both of:

```bash
DOT_MEMORY_TRACE=1 DOT_DEPLOY_VERBOSE=1 dot deploy ...
DOT_MEMORY_TRACE=1 DOT_DEPLOY_VERBOSE=1 playground deploy ...
```

- `DOT_MEMORY_TRACE=1` streams a per-second `rss / heap / external / peak` sample to stderr from the watchdog worker. The worker has its own event loop, so samples keep firing even while the main thread is busy — perfect for capturing the timeline of a leak.
Expand All @@ -139,7 +139,7 @@ pnpm build

### Local Install

Compile and install the `dot` binary to `~/.polkadot/bin/`:
Compile and install the `playground` binary (plus the `pg` alias) to `~/.polkadot/bin/`:

```bash
pnpm cli:install
Expand All @@ -152,7 +152,7 @@ pnpm test # unit tests, one-shot
pnpm test:watch # rerun on change
npx tsc --noEmit # type check

pnpm test:e2e # E2E tests (slow; run `dot init` first)
pnpm test:e2e # E2E tests (slow; run `playground init` first)
```

#### Unit tests
Expand All @@ -163,7 +163,7 @@ Live alongside the code as `*.test.ts`. They avoid mocking so deeply that they j

Live under `e2e/cli/*.test.ts`, with a separate `e2e/vitest.config.ts`. Each test spawns the CLI via `bun run src/index.ts` (execa wrapper in `e2e/cli/helpers/dot.ts`) and asserts on stdout/stderr/exit code. Files run serially — they share a single deployer account on Paseo and would race otherwise.

Prerequisite: run `dot init` once to install the required local deps (mainly Kubo IPFS for the deploy pipeline). Tests also reach Paseo Asset Hub and `codeload.github.com` over the internet, so they need network.
Prerequisite: run `playground init` once to install the required local deps (mainly Kubo IPFS for the deploy pipeline). Tests also reach Paseo Asset Hub and `codeload.github.com` over the internet, so they need network.

CI runs the suite on every PR, on push to `main`, and daily at 06:00 UTC (`.github/workflows/e2e.yml`).

Expand Down
2 changes: 1 addition & 1 deletion docs/e2e-running-tests.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ a stable release is cut. Tag: `e2e-ci-release`.
**e2e-post-release.yml** (`release: published`, stable only — `prerelease != true`,
or `workflow_dispatch`):
Runs `install.sh` using the pinned tag (`VERSION=<tag> curl … | bash`), verifies
the binary lands at `~/.polkadot/bin/dot`, then runs `published.test.ts` against
the binary lands at `~/.polkadot/bin/playground`, then runs `published.test.ts` against
the installed binary. Catches `install.sh` regressions that the SEA-download path
doesn't exercise. Tag: `e2e-ci-post-release`.

Expand Down
6 changes: 3 additions & 3 deletions e2e/cli/install.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ describe("dot install", () => {
test("dot update reports a meaningful outcome", async () => {
const result = await dot(["update"]);
expect(result.exitCode).toBe(0);
// Without this, a regression where `dot update` silently no-ops is
// Without this, a regression where `playground update` silently no-ops is
// invisible. Match either exact wording from src/commands/update.ts:
// "already on latest (vX.Y.Z)" — when current === latest tag
// "Updated dot to vX.Y.Z" — when an update happened
// "Updated playground to vX.Y.Z" — when an update happened
// Both branches print "Checking for updates..." first, so anchor on
// the outcome line.
expect(result.stdout).toMatch(/already on latest \(v|Updated dot to v/);
expect(result.stdout).toMatch(/already on latest \(v|Updated playground to v/);
// Verify the binary still works after the update reported success.
const version = await dot(["--version"]);
expect(version.exitCode).toBe(0);
Expand Down
48 changes: 35 additions & 13 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@ set -e

INSTALL_DIR="$HOME/.polkadot"
REPO="paritytech/playground-cli"
BIN="dot"
# The command users invoke, plus a short alias. Both resolve to the same binary.
CMD="playground"
ALIAS="pg"
# Release artifacts are still published as `dot-<os>-<arch>` — keep the asset
# prefix in sync with .github/workflows/release.yml. The downloaded file is
# saved locally under $CMD, so the old `dot` command name is gone.
ASSET_PREFIX="dot"

# 1) Detect platform
OS=$(uname -s); case "$OS" in Linux) OS=linux;; Darwin) OS=darwin;; *) echo "Unsupported OS: $OS"; exit 1;; esac
ARCH=$(uname -m); case "$ARCH" in x86_64|amd64) ARCH=x64;; arm64|aarch64) ARCH=arm64;; *) echo "Unsupported arch: $ARCH"; exit 1;; esac
ASSET="$BIN-$OS-$ARCH"
ASSET="$ASSET_PREFIX-$OS-$ARCH"

# 2) Resolve release tag
#
Expand All @@ -33,21 +39,27 @@ fi

# 3) Install binary
spin() { while true; do for c in '|' '/' '-' '\'; do printf "\r%s %s" "$1" "$c"; sleep 0.1; done; done; }
spin "Installing dot ($OS/$ARCH) $TAG" &
spin "Installing $CMD ($OS/$ARCH) $TAG" &
SPIN_PID=$!
trap "kill $SPIN_PID 2>/dev/null" EXIT

mkdir -p "$INSTALL_DIR/bin" "$HOME/.local/bin"
curl -fsSL "https://github.com/$REPO/releases/download/$TAG/$ASSET" -o "$INSTALL_DIR/bin/$BIN"
chmod +x "$INSTALL_DIR/bin/$BIN"
curl -fsSL "https://github.com/$REPO/releases/download/$TAG/$ASSET" -o "$INSTALL_DIR/bin/$CMD"
chmod +x "$INSTALL_DIR/bin/$CMD"
if [ "$OS" = "darwin" ]; then
codesign --sign - --force "$INSTALL_DIR/bin/$BIN" 2>/dev/null || true
xattr -c "$INSTALL_DIR/bin/$BIN" 2>/dev/null || true
codesign --sign - --force "$INSTALL_DIR/bin/$CMD" 2>/dev/null || true
xattr -c "$INSTALL_DIR/bin/$CMD" 2>/dev/null || true
fi
ln -sf "$INSTALL_DIR/bin/$BIN" "$HOME/.local/bin/$BIN"
# Expose both the full command and its short alias from both bin dirs on PATH.
ln -sf "$INSTALL_DIR/bin/$CMD" "$INSTALL_DIR/bin/$ALIAS"
ln -sf "$INSTALL_DIR/bin/$CMD" "$HOME/.local/bin/$CMD"
ln -sf "$INSTALL_DIR/bin/$CMD" "$HOME/.local/bin/$ALIAS"
# Remove the legacy `dot` binary/symlink from earlier installs so it stops
# resolving on PATH — the command is now `playground` (or `pg`).
rm -f "$INSTALL_DIR/bin/dot" "$HOME/.local/bin/dot"

kill $SPIN_PID 2>/dev/null; wait $SPIN_PID 2>/dev/null || true; trap - EXIT
printf "\rInstalled dot (%s/%s) %s \n" "$OS" "$ARCH" "$TAG"
printf "\rInstalled %s (%s/%s) %s \n" "$CMD" "$OS" "$ARCH" "$TAG"

# 4) Add to PATH
append_once() {
Expand All @@ -69,12 +81,22 @@ fi
export PATH="$INSTALL_DIR/bin:$HOME/.local/bin:$PATH"

echo ""
echo -e "dot is ready! Setting up dependencies…"
echo -e "$CMD is ready! Setting up dependencies…"
echo ""
if ! "$INSTALL_DIR/bin/$BIN" init --yes; then
if ! "$INSTALL_DIR/bin/$CMD" init --yes; then
INIT_EXIT=$?
echo -e "\n\033[33mDependency setup failed. Run \033[1mdot init\033[0;33m when ready.\033[0m" >&2
echo -e "\n\033[33mDependency setup failed. Run \033[1m$CMD init\033[0;33m (or \033[1m$ALIAS init\033[0;33m) when ready.\033[0m" >&2
exit "$INIT_EXIT"
fi

# Final "what to run next" prompt, styled to match the yellow rounded-border
# Callout the TUI uses for phone-signing notifications (see
# src/utils/ui/theme/Callout.tsx). Mirrored in bash so it shows the moment the
# curl install finishes.
Y='\033[33m'; B='\033[1m'; R='\033[0m'
echo ""
echo -e "Run \033[1mdot init\033[0m to log in with the Polkadot mobile app."
echo -e "${Y}╭─ ${B}next step${R}${Y} ──────────────────────────────╮${R}"
echo -e "${Y}│${R} Run ${B}$CMD init${R} or ${B}$ALIAS init${R} to log in ${Y}│${R}"
echo -e "${Y}│${R} with the Polkadot mobile app. ${Y}│${R}"
echo -e "${Y}│${R} Both commands work the same. ${Y}│${R}"
echo -e "${Y}╰──────────────────────────────────────────╯${R}"
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@
"type": "module",
"packageManager": "pnpm@10.32.1",
"bin": {
"dot": "./src/index.ts"
"playground": "./src/index.ts",
"pg": "./src/index.ts"
},
"scripts": {
"postinstall": "bash node_modules/@parity/product-sdk-terminal/scripts/patch-wasm.sh",
"format": "biome format --write .",
"format:check": "biome format .",
"lint:license": "./scripts/check-license-headers.sh",
"build": "bun build --compile src/index.ts --outfile ./dist/dot",
"cli:install": "bun build --compile src/index.ts --outfile ~/.polkadot/bin/dot",
"build": "bun build --compile src/index.ts --outfile ./dist/playground",
"cli:install": "bun build --compile src/index.ts --outfile ~/.polkadot/bin/playground && ln -sf ~/.polkadot/bin/playground ~/.polkadot/bin/pg",
"test": "vitest run",
"test:watch": "vitest",
"test:e2e": "vitest run --config e2e/vitest.config.ts",
Expand Down
Loading
Loading