Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
2ccb804
update `e2e-local.sh` to not break local PNPM_HOME
charlesHetterich May 4, 2026
93c8a40
added fixture rust contract project
charlesHetterich May 4, 2026
37c045a
updated e2e ci setup to use the correct branch of cargo-pvm-contract
charlesHetterich May 4, 2026
bc7bf2c
improved e2e test logs in CI so errors in downstream dependencies sho…
charlesHetterich May 4, 2026
4558ead
fix(e2e): use nightly Rust for CDM contract toolchain
EnderOfWorlds007 May 5, 2026
985110e
fix(e2e): skip CDM test when cargo-pvm-contract is absent; warn in lo…
EnderOfWorlds007 May 5, 2026
7373966
update bulletin-deploy
charlesHetterich May 5, 2026
2f75983
Merge remote-tracking branch 'origin/main' into ch/cdm-e2e
charlesHetterich May 5, 2026
e4159fc
update dotns e2e test urls to be personhood-lite compatible
charlesHetterich May 5, 2026
0a519cb
added required .cargo files for rust fixture project
charlesHetterich May 5, 2026
2bbec28
update to latest bulletin-deploy
charlesHetterich May 5, 2026
60b8d01
fix e2e test setup
charlesHetterich May 5, 2026
4514cf3
test: use prebuilt CDM artifacts in e2e
charlesHetterich May 5, 2026
a347cb3
fix: discover prebuilt CDM artifacts without cargo
charlesHetterich May 5, 2026
b7fcf20
ci: run CDM deploy cell first
charlesHetterich May 5, 2026
0545ccd
format
charlesHetterich May 5, 2026
a5bb038
ci: install CDM toolchain for deploy cell
charlesHetterich May 5, 2026
7b3c7d6
ci: add cdm bin path after install
charlesHetterich May 6, 2026
61dc5d9
update readme
charlesHetterich May 6, 2026
82f38ad
add changeset
charlesHetterich May 6, 2026
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/bump-bulletin-deploy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"playground-cli": patch
---

Update `bulletin-deploy` to 0.7.10 for the latest DotNS/deploy fixes.
26 changes: 22 additions & 4 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,23 +94,41 @@ jobs:
max-parallel: 1 # serial — share SIGNER + registry domains
matrix:
include:
- cell: pr-deploy-cdm
# source: e2e/cli/deploy.test.ts → describe("dot deploy — cdm …")
pattern: "deploy — cdm"
- cell: pr-deploy-frontend
# source: e2e/cli/deploy.test.ts → describe("dot deploy --playground — full pipeline …")
pattern: "full pipeline"
- cell: pr-deploy-foundry
# source: e2e/cli/deploy.test.ts → describe("dot deploy — foundry …")
pattern: "deploy — foundry"
# pr-deploy-cdm dropped pending fixture upgrade — see the
# describe.skip block in e2e/cli/deploy.test.ts. Phase 5
# follow-up will land a real flipper fixture and re-add
# this cell.
steps:
# checkout must run before the local composite action can be loaded.
- uses: actions/checkout@v4

- id: setup
uses: ./.github/actions/setup-e2e

- name: Install Rust/CDM toolchain
if: matrix.cell == 'pr-deploy-cdm'
shell: bash
run: |
sudo apt-get update -q
sudo apt-get install -y -q --no-install-recommends build-essential pkg-config
if ! command -v rustup >/dev/null 2>&1; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
source "$HOME/.cargo/env"
fi
rustup toolchain install nightly --profile minimal --component rust-src
rustup default nightly
curl -fsSL https://raw.githubusercontent.com/paritytech/contract-dependency-manager/main/install.sh | bash
echo "$HOME/.cdm/bin" >> "$GITHUB_PATH"
export PATH="$HOME/.cdm/bin:$PATH"
command -v cdm
cargo pvm-contract --help

- name: Run E2E cell (one retry on transient testnet failures)
uses: nick-fields/retry@v3
with:
Expand Down
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ These are things that aren't self-evident from reading the code and have bitten
- **Signer mode selection lives in one file** (`src/utils/deploy/signerMode.ts`). The mainnet rewrite is a single-file swap; keep that boundary clean.
- **`src/utils/deploy/*` and `src/utils/build/*` must not import React or Ink.** They form the SDK surface that RevX consumes from a WebContainer. TUI code lives in `src/commands/*/`.
- **Bun compiled-binary stdin quirk** — Ink's `useInput` silently drops every keystroke (arrows, Enter, Ctrl+C) in `bun build --compile` binaries unless `process.stdin.on('readable', …)` is touched before Ink's `render()`. We install a no-op `readable` listener at the top of `src/index.ts` as a warm-up. Do NOT remove it until Bun's compiled-binary TTY stdin behaves like Node's. Symptom if this breaks: TUI renders but nothing responds, including Ctrl+C.
- **`bulletin-deploy` 0.7.4+ pulls in a transitive dep with a broken publish manifest** that pnpm refuses to install. `@parity/dotns-cli@0.5.6`'s published `package.json` declares `"@polkadot-api/descriptors": "file:.papi/descriptors"` — a workspace-only path that doesn't exist in the published tarball. npm tolerates the dangling `file:` reference (creates a broken symlink and continues); pnpm's strict resolver fails with `ERR_PNPM_LINKED_PKG_DIR_NOT_FOUND`. We work around it with a `pnpm.overrides` entry in `package.json` pointing the offending sub-dep at a tiny stub package (`stubs/papi-descriptors-stub/`) so resolution succeeds. The dep is functionally vestigial — dotns-cli's `dist/cli.js` is fully-bundled (Bun build, no externals) and never imports `@polkadot-api/descriptors` at runtime, so the stub exporting `{}` is correct. **Remove the override + stub once `@parity/dotns-cli` republishes a clean manifest.** Tracked upstream against `paritytech/dotns-sdk`.
- **`bulletin-deploy` is pinned to an explicit version, not `latest`.** We're on `0.7.6` stable today. The `latest` npm dist-tag is a moving target and previously pointed at 0.6.8, which has a WebSocket heartbeat bug (default 40s < chunk timeout 60s) that tears down uploads mid-flight as `WS halt (3)`. Keep the pin explicit so we never silently slide onto a broken `latest`. When upgrading, read the release notes for any public-API changes to `deploy()`, `DotNS` methods, or the `DeployOptions` we rely on (`jsMerkle`, `signer`, `signerAddress`, `mnemonic`, `rpc`, `attributes`). Note: 0.7.0 removed the `playground?: boolean` `DeployOption` (registry publishing now lives here in `src/utils/deploy/playground.ts`), which is a no-op for us since we never passed that flag. 0.7.1 made the memory-report teardown Bun-safe upstream. 0.7.2 bumped the default `CHUNK_TIMEOUT_MS` 60s → 180s to match Bulletin's new 24s Aura slot duration; `BULLETIN_CHUNK_TIMEOUT_MS` override still works. 0.7.4 extracted the dotns logic into a separate `@parity/dotns-cli` subprocess (forked via `_require.resolve("@parity/dotns-cli")`); see the publish-bug workaround note above. 0.7.4 also moved label classification off the `DotNS` instance — the previously-instance method `dotns.classifyName(label)` is now the top-level pure function `classifyDotnsLabel(label)`, and the result field renamed `requiredStatus` → `status`. The function isn't re-exported from the package root, so `src/utils/deploy/availability.ts` mirrors the (small, stable) logic locally as `classifyLabel` — same precedent as `simulateUserStatus`. 0.7.6 added ambient Sentry mode for host apps; keep the CLI-owned privacy gate in `src/bootstrap.ts`.
- **`bulletin-deploy` 0.7.4+ pulls in a transitive dep with a broken publish manifest** that pnpm refuses to install. `@parity/dotns-cli@0.6.0`'s published `package.json` declares `"@polkadot-api/descriptors": "file:.papi/descriptors"` — a workspace-only path that doesn't exist in the published tarball. npm tolerates the dangling `file:` reference (creates a broken symlink and continues); pnpm's strict resolver fails with `ERR_PNPM_LINKED_PKG_DIR_NOT_FOUND`. We work around it with a `pnpm.overrides` entry in `package.json` pointing the offending sub-dep at a tiny stub package (`stubs/papi-descriptors-stub/`) so resolution succeeds. The dep is functionally vestigial — dotns-cli's `dist/cli.js` is fully-bundled (Bun build, no externals) and never imports `@polkadot-api/descriptors` at runtime, so the stub exporting `{}` is correct. **Remove the override + stub once `@parity/dotns-cli` republishes a clean manifest.** Tracked upstream against `paritytech/dotns-sdk`.
- **`bulletin-deploy` is pinned to an explicit version, not `latest`.** We're on `0.7.9` stable today. The `latest` npm dist-tag is a moving target and previously pointed at 0.6.8, which has a WebSocket heartbeat bug (default 40s < chunk timeout 60s) that tears down uploads mid-flight as `WS halt (3)`. Keep the pin explicit so we never silently slide onto a broken `latest`. When upgrading, read the release notes for any public-API changes to `deploy()`, `DotNS` methods, or the `DeployOptions` we rely on (`jsMerkle`, `signer`, `signerAddress`, `mnemonic`, `rpc`, `attributes`). Note: 0.7.0 removed the `playground?: boolean` `DeployOption` (registry publishing now lives here in `src/utils/deploy/playground.ts`), which is a no-op for us since we never passed that flag. 0.7.1 made the memory-report teardown Bun-safe upstream. 0.7.2 bumped the default `CHUNK_TIMEOUT_MS` 60s → 180s to match Bulletin's new 24s Aura slot duration; `BULLETIN_CHUNK_TIMEOUT_MS` override still works. 0.7.4 extracted the dotns logic into a separate `@parity/dotns-cli` subprocess (forked via `_require.resolve("@parity/dotns-cli")`); see the publish-bug workaround note above. 0.7.4 also moved label classification off the `DotNS` instance — the previously-instance method `dotns.classifyName(label)` is now the top-level pure function `classifyDotnsLabel(label)`, and the result field renamed `requiredStatus` → `status`. The function isn't re-exported from the package root, so `src/utils/deploy/availability.ts` mirrors the (small, stable) logic locally as `classifyLabel` — same precedent as `simulateUserStatus`. 0.7.6 added ambient Sentry mode for host apps; keep the CLI-owned privacy gate in `src/bootstrap.ts`. 0.7.9 includes the DotNS/deploy fixes needed by the CDM E2E path.
- **Throttle TUI info updates** — bulletin-deploy logs per-chunk and builds (vite/next) stream thousands of lines/sec. Calling `setState` on every log event floods React's reconciler with so much backpressure the process can balloon past 20 GB and freeze the OS. `RunningStage` coalesces "latest info" updates to ≤10/sec via a ref + timer and caps line length at 160 chars. Any new hot-path event sink should do the same; don't hook raw per-line streams directly into Ink state.
- **Process-guard safety net** (`src/utils/process-guard.ts`) — deploy pipelines open several long-lived WebSockets + child processes and any one of them can keep the event loop alive after the TUI visibly finishes, turning `dot` into a zombie that accumulates retry buffers indefinitely (seen climbing past 25 GB). We defend in depth: (1) `installSignalHandlers()` catches SIGINT/TERM/HUP + `unhandledRejection` and forces cleanup + exit within 3 s; (2) `scheduleHardExit()` installs an `unref`'d timer that kills the process if the event loop doesn't drain within a grace period; (3) `startMemoryWatchdog()` aborts if RSS exceeds 4 GB — a generous cap because legit deploys on Bun SEA binaries routinely touch 1–1.5 GB from runtime-metadata decoding + Bun's JSC heap + Ink yoga. Do NOT re-add a per-window growth detector: we tried 300 MB / 3 s and it false-positived on the single-burst metadata-loading spike, aborting deploys that would have succeeded. Set `DOT_MEMORY_TRACE=1` to stream per-sample RSS/heap/external stats — useful when diagnosing a real leak report. **Telemetry bootstrap** (`src/bootstrap.ts`) is the FIRST import in `src/index.ts`. It sets `BULLETIN_DEPLOY_USE_AMBIENT_SENTRY=1` and `BULLETIN_DEPLOY_HOST_APP=playground-cli` before `bulletin-deploy` can evaluate, then maps `DOT_TELEMETRY`/internal-context detection to `BULLETIN_DEPLOY_TELEMETRY`. Do not leave `BULLETIN_DEPLOY_TELEMETRY` unset while setting the host app: `bulletin-deploy@0.7.6` treats `playground-cli` as an internal host, which would enable deploy telemetry for external users. `BULLETIN_DEPLOY_MEM_REPORT` is not forced off by default anymore because upstream guards the Bun-incompatible memory-report path. Any new long-running command should register a cleanup hook via `onProcessShutdown()`.
- **Process-guard safety net** (`src/utils/process-guard.ts`) — deploy pipelines open several long-lived WebSockets + child processes and any one of them can keep the event loop alive after the TUI visibly finishes, turning `dot` into a zombie that accumulates retry buffers indefinitely (seen climbing past 25 GB). We defend in depth: (1) `installSignalHandlers()` catches SIGINT/TERM/HUP + `unhandledRejection` and forces cleanup + exit within 3 s; (2) `scheduleHardExit()` installs an `unref`'d timer that kills the process if the event loop doesn't drain within a grace period; (3) `startMemoryWatchdog()` aborts if RSS exceeds 4 GB — a generous cap because legit deploys on Bun SEA binaries routinely touch 1–1.5 GB from runtime-metadata decoding + Bun's JSC heap + Ink yoga. Do NOT re-add a per-window growth detector: we tried 300 MB / 3 s and it false-positived on the single-burst metadata-loading spike, aborting deploys that would have succeeded. Set `DOT_MEMORY_TRACE=1` to stream per-sample RSS/heap/external stats — useful when diagnosing a real leak report. **Telemetry bootstrap** (`src/bootstrap.ts`) is the FIRST import in `src/index.ts`. It sets `BULLETIN_DEPLOY_USE_AMBIENT_SENTRY=1` and `BULLETIN_DEPLOY_HOST_APP=playground-cli` before `bulletin-deploy` can evaluate, then maps `DOT_TELEMETRY`/internal-context detection to `BULLETIN_DEPLOY_TELEMETRY`. Do not leave `BULLETIN_DEPLOY_TELEMETRY` unset while setting the host app: `bulletin-deploy` treats `playground-cli` as an internal host, which would enable deploy telemetry for external users. `BULLETIN_DEPLOY_MEM_REPORT` is not forced off by default anymore because upstream guards the Bun-incompatible memory-report path. Any new long-running command should register a cleanup hook via `onProcessShutdown()`.
- **Parser MUST NOT emit an event per log line.** `DeployLogParser.feed()` is called for every console line bulletin-deploy prints — hundreds per deploy on the happy path, thousands if retries fire. We intentionally emit events ONLY for phase-banner matches and `[N/M]` chunk progress. Everything else returns `null`. Adding a catch-all `info` emit turns the parser into a firehose that allocates ~200 bytes × thousands of lines, and was a measurable contributor to chunk-upload memory pressure.
- **`dot mod` is GitHub-tarball-only and must stay that way.** `src/utils/mod/source.ts` downloads from `codeload.github.com` (no auth, no `git`/`gh` required for the public-repo case) and extracts via `node:zlib` + the pure-JS `tar` package. Do NOT re-introduce `git clone` or `gh repo fork` paths — both would re-add a hard tooling requirement and the fork path was specifically removed because GitHub caps you to one fork per source-repo per account, which broke "mod the same starter twice." A non-modable app (no `metadata.repository`) returns a hard error from `dot mod`; the interactive picker filters those out so the user never sees an unmoddable option. The picker does NOT pre-probe each app's repo visibility, because that would burn the 60 req/hr anonymous GitHub API quota on every `dot mod`. Instead, `runModCommand` lazy-probes the picked app once via `assertPublicGitHubRepo()` between picker dismount and `SetupScreen` mount; `dot deploy --modable` already rejects private repos at deploy time, so this fires only when a publisher has flipped visibility post-publish.
- **`ensureGhAuthed()` does NOT shell out to `gh auth login`.** Even from the interactive deploy, Ink owns stdout + raw-mode stdin and a `stdio: "inherit"` child would race Ink's `useInput` for keystrokes — producing garbled output and dropped key events. Both interactive and non-interactive paths that need GitHub repo creation fail with the same actionable message: run `gh auth login` once outside `dot`, then retry. Existing `origin` URLs do not require `gh` auth. Properly suspending Ink to hand off stdio is possible but requires unmounting + re-rendering with state preservation, and we deemed that complexity not worth it for a one-time-per-machine speedbump. If you ever do implement it, the wiring point is `ModablePreflightStage` in `src/commands/deploy/DeployScreen.tsx`.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ pnpm format:check # check only

- `@polkadot-apps/*` are pinned to `latest` intentionally — they are our own packages and we want the lockfile to track head.
- `@polkadot-api/sdk-ink` is pinned to `^0.6.2` and `polkadot-api` to `^1.23.3` because `chain-client` currently embeds an internal `PolkadotClient` shape that breaks with newer versions. Bump together with `chain-client` only.
- `bulletin-deploy` is pinned to an explicit version — not `latest`. Currently `0.7.6`. Previously `latest` pointed at 0.6.8 which had a WebSocket heartbeat bug (40s default < 60s chunk timeout) that tore chunk uploads down as `WS halt (3)`; keeping the pin explicit avoids ever sliding back onto that. When bumping, check the release notes for any changes to `deploy()` / `DotNS` APIs we rely on.
- `bulletin-deploy` is pinned to an explicit version — not `latest`. Currently `0.7.10`. Previously `latest` pointed at 0.6.8 which had a WebSocket heartbeat bug (40s default < 60s chunk timeout) that tore chunk uploads down as `WS halt (3)`; keeping the pin explicit avoids ever sliding back onto that. When bumping, check the release notes for any changes to `deploy()` / `DotNS` APIs we rely on.

## Architecture Highlights

Expand Down
14 changes: 3 additions & 11 deletions e2e/cli/deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -426,17 +426,9 @@ describe("dot deploy — rejects --no-contract-build with no artefacts", () => {
});
});

// SKIPPED: the rust-cdm fixture's `target/flipper.contract` is a stub
// (`{"source":{"hash":"0xabc"}}`) and there is no `target/<crate>.release.polkavm`
// for the skip-build path to read. A working fixture needs:
// 1. a real `src/lib.rs` so `cargo metadata` parses the manifest
// (currently fails: "no targets specified in the manifest")
// 2. a committed `target/<crate>.release.polkavm` produced by an
// actual `cargo-contract build` of a minimal flipper contract
// Tracked as Phase 5 follow-up. Until then, CDM detection is covered by
// the preflight test in `dot deploy — preflight and validation` and the
// skip-build path itself is unit-tested in `src/utils/deploy/contracts.test.ts`.
describe.skip("dot deploy — CDM (requires Paseo + IPFS)", () => {
// CDM follows the same CI shape as foundry/hardhat: deploy pre-built artifacts
// committed with the fixture, without requiring the Rust/PVM toolchain on CI.
describe("dot deploy — cdm (requires Paseo + IPFS)", () => {
test("CDM deploy completes end-to-end", { timeout: 450_000 }, async () => {
const domain = E2E_DOMAINS.cdm;
const result = await dot([
Expand Down
22 changes: 13 additions & 9 deletions e2e/cli/fixtures/accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ export const BOB: TestAccount = devAccount("Bob");
* publish path (storage / re-deploy / cross-owner collision); reuse a single
* domain for the preflight / validation tests.
*
* DotNS classifies names with a 6-8 character base plus exactly two trailing
* digits as PopLite. Keep these labels in that shape so E2E tests do not
* accidentally require Full personhood on testnet.
*
* NOTE: do not assert on the registry state of `preflight` — it's shared by
* six tests in the same file and the metadata at any moment reflects whichever
* one ran last. Stdout assertions are fine. If you need to assert on registry
Expand All @@ -86,28 +90,28 @@ export const E2E_DOMAINS = {
* tests do reach `registry.publish`. SIGNER ends up owning this domain
* regardless; subsequent runs are same-owner re-publishes.
*/
preflight: "e2e-cli-preflight",
preflight: "e2epre00",
/** Used by the storage-phase happy path. */
storage: "e2e-cli-storage",
storage: "e2estr00",
/** Used by the same-owner re-deploy test. */
redeploy: "e2e-cli-redeploy",
redeploy: "e2ered00",
/** Used by the cross-owner collision test (BOB tries to take SIGNER's). */
collision: "e2e-cli-collision",
collision: "e2ecol00",
/**
* Phase 3 cell domains — registered by `tools/register-e2e-fixtures.ts`.
* Owned by SIGNER; subsequent runs are same-owner re-publishes.
* Not yet wired to any test — see Phase 4 of docs-internal/2026-05-02-e2e-test-suite-design.md.
*/
foundry: "e2e-cli-foundry",
cdm: "e2e-cli-cdm",
hardhat: "e2e-cli-hardhat",
multi: "e2e-cli-multi",
foundry: "e2efnd00",
cdm: "e2ecdm00",
hardhat: "e2ehat00",
multi: "e2emul00",
/**
* Used by the nightly-chaos-sigint cell only. The deploy is interrupted by
* SIGINT before it completes, so this domain is never actually registered.
* It is kept separate from `storage` to avoid any race with the happy-path
* storage test in test-publish when both run in a nightly that triggers all
* matrices.
*/
chaos: "e2e-cli-chaos",
chaos: "e2echs00",
} as const;
9 changes: 9 additions & 0 deletions e2e/cli/fixtures/projects/rust-cdm/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[build]
target = ".cargo/riscv64emac-unknown-none-polkavm.json"

[unstable]
build-std = ["core", "alloc"]
json-target-spec = true

[env]
RUSTC_BOOTSTRAP = "1"
Loading
Loading