cli: add --json structured-output mode#885
Draft
kcsongor wants to merge 14 commits into
Draft
Conversation
kcsongor
force-pushed
the
cs/ntt-json-output
branch
from
May 20, 2026 15:31
7b1c095 to
f271ba6
Compare
- bind OutboxItem to manager instance - decouple BPF upgrade authority from Config owner
Multi-tenant Solana NTT (>= v4) PDAs are scoped by the per-deployment Instance pubkey, so every CLI surface that reads or writes a manager needs to know it: - ChainConfig grows an optional `instance` field; SolanaDeploymentResult threads it back from `deploy()`. - pullChainConfig / nttFromManager take an optional `solanaInstance` arg, plumbed through every caller (add-chain, upgrade, clone, transfer-ownership, set-mint-authority, solana subcommands, config-mgmt's pull loop). - deploySvm gains a multi-tenant branch: generates (or loads via --instance-key) an Instance keypair, sets contracts.ntt.instance before SDK construction, co-signs `initialize`, and returns the instance pubkey alongside the program address. addSolanaInstance no longer rethrows on initializeOrUpdateLUT failure (matches deploySvm's swallow-on-LUT shape; lets dev environments without the wormhole core bridge still write deployment.json). - `ntt solana token-authority --instance <pubkey>` derives the per-instance PDA before mint-authority handoff. SolanaNtt's constructor now refuses the v4-without-instance and v3-with-instance footguns at construction time (the PDA factory accepts an optional config arg for back-compat, so without this the SDK silently falls back to legacy singleton derivations against a multi-tenant manager). Old "isV4" branches renamed to "multiTenant" in cli/src/solana/deploy.ts to capture the property we're checking. Local cli/test/solana.sh: - export COPYFILE_DISABLE=1 (macOS' AppleDouble metadata files break solana-test-validator's genesis-archive unpacker). - airdrop with --commitment finalized — `solana program deploy` uses --commitment finalized and was racing finalization on a fresh airdrop, surfacing as a bogus "insufficient funds" against a 50-SOL account. - new v4 multi-tenant section: asserts `ntt upgrade` is blocked at the v3->v4 boundary by canUpgrade(), then patches Anchor.toml + lib.rs to declare a locally-keypair'd id, rebuilds, deploys, and walks `add-chain --instance-of` end-to-end. cleanup() trap unconditionally restores the patched source files on exit.
cli/e2e/e2e-solana.test.ts spins up its own `solana-test-validator` loaded with the wormhole core bridge + post-message shim + verify-vaa shim as genesis programs (mirroring solana/Anchor.toml's [[test.genesis]] setup) plus the local v4 NTT .so at its declared id, then drives `ntt` end-to-end via Bun.spawn. Three tests: - `ntt init Mainnet` writes deployment.json with the expected shape. - `add-chain --instance-of` creates a multi-tenant Instance under the pre-loaded program (skipping deploy) and persists the `instance` pubkey alongside `manager` in deployment.json. - `ntt upgrade Solana --ver 4.0.0` from a synthetic v3 deployment.json is blocked by canUpgrade() with the migration-steer error message. Logging knobs: NTT_E2E_DEBUG=1 one progress line per `ntt` invocation NTT_E2E_VERBOSE=1 full stdout+stderr per invocation On failure, full stdout+stderr is dumped through the thrown error. The validator's stdout/stderr is unconditionally appended to /tmp/ntt-e2e-validator.log for `tail -f`-style real-time inspection. Per-test timeouts are set in-file so `bun test cli/e2e/e2e-solana.test.ts` runs without a `--timeout` flag: validator boot ~10s, full add-chain ~70s, upgrade-block <1s. cli/src/index.ts: `.parseAsync().then(() => process.exit(0))` instead of `.parse()`. Without the explicit exit, the Solana SDK's `Connection` leaves a websocket subscription open after a successful command and the CLI hangs indefinitely waiting for an event-loop drain that won't come. This bites real users too — `ntt add-chain`/`upgrade`/`push` exiting cleanly is what everyone expects. .github/workflows/cli.yml: adds `test-cli-solana-e2e` job mirroring solana.yml's `anchor-test` setup (bun 1.3.4, solana 1.18.26, anchor 0.29.0) plus `make sdk` to produce the v4 .so, then runs the bun suite. Uploads /tmp/ntt-e2e-validator.log as an artifact on failure so CI-only flakes are debuggable.
The CLI imports several @wormhole-foundation/sdk-*-ntt workspace
packages that resolve via bun's workspace symlinks under root
node_modules. Without 'bun ci' at the repo root, those symlinks
don't exist and 'ntt' fails on first import:
Cannot find module '@wormhole-foundation/sdk-evm-ntt'
from cli/src/index.ts
Mirrors the pattern in test-cli-unit, which already does 'bun ci'
first.
`bun run --cwd cli test:e2e` was a glob over `e2e/*.test.ts`, which
meant the EVM job in cli.yml started running the new Solana e2e
suite too. The EVM CI runner has no `solana-test-validator`, so
the suite errored out in beforeAll and the job failed.
Split into:
test:e2e:evm — anvil-only (used by the EVM CI job)
test:e2e:solana — solana-test-validator-only (used locally;
test-cli-solana-e2e runs the file directly)
test:e2e — both, sequentially (for local 'run everything')
`make sdk` calls `bun run build:solana` which only builds sdk-definitions-ntt + sdk-solana-ntt. The CLI also imports @wormhole-foundation/sdk-evm-ntt and sdk-sui-ntt — without their dist/ populated, bun resolves the workspace symlink to a package whose main/module fields point at non-existent files and the resolver surfaces it as 'Cannot find module sdk-evm-ntt'. Replace `make sdk` with `make anchor-build` (produces the .so and patches the IDL — what we actually need from the solana side) plus `bun run build` at the repo root, which builds every workspace package's TypeScript. Mirrors what cli/install.sh does for the EVM e2e job.
kcsongor
force-pushed
the
cs/solana-multi-host
branch
from
June 3, 2026 12:55
0ad1b81 to
44239cf
Compare
kcsongor
force-pushed
the
cs/ntt-json-output
branch
from
June 3, 2026 12:56
f271ba6 to
76f2c77
Compare
kcsongor
force-pushed
the
cs/ntt-json-output
branch
from
June 3, 2026 13:05
76f2c77 to
11bf414
Compare
…transfer-ownership
Adds a global --json flag (also activated by WL_NTT_JSON=1) that:
- Redirects console.log / .info / .warn / .debug and direct process.stdout
writes to stderr.
- Emits exactly one { ok, command, data } envelope on stdout from the
command's success path via emitResult().
Failure paths are intentionally unstructured: non-zero exit + stderr
message is the contract. Wrappers (scripts, parent processes) read the
trailing stdout line on exit code 0; treat anything else as failure.
Bun's console implementation doesn't always route through
process.stdout.write, so we override the console methods directly *and*
hijack process.stdout.write for transitive deps.
Lets parent processes consume the per-instance token_authority PDA without
text-scraping stdout. Same { ok, command, data } envelope shape as the
other --json-aware commands.
kcsongor
force-pushed
the
cs/ntt-json-output
branch
from
June 3, 2026 19:26
11bf414 to
68ca889
Compare
kcsongor
force-pushed
the
cs/solana-multi-host
branch
2 times, most recently
from
June 19, 2026 12:02
de8534b to
63910c4
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a
--jsonflag (also activated byWL_NTT_JSON=1) to the NTT CLI commands that parent processes / scripts most often need to drive:ntt initntt add-chain(both the regular and--instance-ofmulti-tenant paths)ntt push(regular +--dangerously-transfer-ownership-in-one-step)ntt transfer-ownershipntt solana token-authorityIn
--jsonmode, every stdout write (including libraryconsole.logs and Bun's native console) is redirected to stderr. The only thing that lands on stdout is exactly one final{ ok: true, command, data }envelope emitted from the command's success path.Failure paths intentionally stay unstructured: non-zero exit + stderr message is the contract. Wrappers read the trailing stdout line on exit 0; treat anything else as failure.
Why draft, why this base
This stack technically doesn't depend on #875 — the
--jsonwork touches commands that exist independent of the multi-tenant changes. But it's easier to keep the branches stacked while #875 is in flight, since #875 is also under active development and rebasing the JSON work onto main and then again onto #875's eventual merge state is fiddly.Don't merge this until #875 is merged. Once #875 lands on main, I'll re-target this PR to main and lift draft.
Test plan
bun test src/__tests__/— full suite still passes (160 pass)src/__tests__/json-output.test.tscovers:--jsonflagWL_NTT_JSON=1env var--jsonntt init --json | jq .,ntt solana token-authority ... --json | jq .ntt add-chain/ntt push/ntt transfer-ownershipagainst a real deployment (deferred until solana: multi-tenant deployment #875 lands)