diff --git a/.github/workflows/ts-ci.yml b/.github/workflows/ts-ci.yml index 4da3f44a6..7898a64a1 100644 --- a/.github/workflows/ts-ci.yml +++ b/.github/workflows/ts-ci.yml @@ -3,10 +3,10 @@ name: TypeScript CI on: pull_request: branches: - - develop + - "**" push: branches: - - develop + - "**" workflow_dispatch: permissions: diff --git a/.github/workflows/ts-standalone-release.yml b/.github/workflows/ts-standalone-release.yml index fca464bf5..4d31ad40b 100644 --- a/.github/workflows/ts-standalone-release.yml +++ b/.github/workflows/ts-standalone-release.yml @@ -3,7 +3,7 @@ name: TypeScript Standalone Artifacts on: push: branches: - - develop + - master workflow_dispatch: permissions: diff --git a/ts/docs/commands/account/activate.md b/ts/docs/commands/account/activate.md index 311e8fe3d..1aec9905a 100644 --- a/ts/docs/commands/account/activate.md +++ b/ts/docs/commands/account/activate.md @@ -26,8 +26,8 @@ Requires the payer account and the master password via `--password-stdin`; watch | `--dry-run` | Build and estimate only; no signature/broadcast, no password. Excludes `--sign-only` / `--build-only` | | `--sign-only` | Build and sign, output the signed hex (feed [`tx broadcast`](../tx/broadcast.md)). Excludes `--dry-run` / `--build-only`; pairs with `--expiration` | | `--build-only` | Build only, output the **unsigned** hex (feed [`tx multisig --create`](../tx/multisig.md)). Excludes `--dry-run` / `--sign-only`; pairs with `--expiration` | -| `--expiration ` | Transaction expiration in ms, up to `86400000` (24h); only with `--sign-only` or `--build-only` | -| `--permission-id ` | Permission group to sign with (default `0`) | +| `--expiration ` | Transaction expiration in ms, up to `86400000` (24h); only with `--sign-only` or `--build-only`; omitted = node default (~60s) | +| `--permission-id ` | Permission group to sign with (0=owner, 1=witness, 2-9=active); default `0` | | `--wait` / `--wait-timeout ` | Poll after broadcast until confirmed/failed (cap default: config `waitTimeoutMs`, built-in 60000) | | `--password-stdin` | Master password from stdin | diff --git a/ts/docs/commands/account/set.md b/ts/docs/commands/account/set.md index 5f9f6789c..8f0e046b6 100644 --- a/ts/docs/commands/account/set.md +++ b/ts/docs/commands/account/set.md @@ -27,8 +27,8 @@ Requires the account and the master password via `--password-stdin`; watch-only | `--dry-run` | Build and estimate only; no signature/broadcast, no password. Excludes `--sign-only` / `--build-only` | | `--sign-only` | Build and sign, output the signed hex (feed [`tx broadcast`](../tx/broadcast.md)). Excludes `--dry-run` / `--build-only`; pairs with `--expiration` | | `--build-only` | Build only, output the **unsigned** hex (feed [`tx multisig --create`](../tx/multisig.md)). Excludes `--dry-run` / `--sign-only`; pairs with `--expiration` | -| `--expiration ` | Transaction expiration in ms, up to `86400000` (24h); only with `--sign-only` or `--build-only` | -| `--permission-id ` | Permission group to sign with (default `0`) | +| `--expiration ` | Transaction expiration in ms, up to `86400000` (24h); only with `--sign-only` or `--build-only`; omitted = node default (~60s) | +| `--permission-id ` | Permission group to sign with (0=owner, 1=witness, 2-9=active); default `0` | | `--wait` / `--wait-timeout ` | Poll after broadcast until confirmed/failed (cap default: config `waitTimeoutMs`, built-in 60000) | | `--password-stdin` | Master password from stdin | diff --git a/ts/docs/commands/contract/deploy.md b/ts/docs/commands/contract/deploy.md index 751d95e4d..1b4d15b08 100644 --- a/ts/docs/commands/contract/deploy.md +++ b/ts/docs/commands/contract/deploy.md @@ -30,7 +30,7 @@ Requires an account. The master password (via `--password-stdin`) is needed only | `--sign-only` | Sign without broadcasting, output the signed hex; excludes `--dry-run` / `--build-only`; pairs with `--expiration` | | `--build-only` | Build only, output the **unsigned** hex; excludes `--dry-run` / `--sign-only`; pairs with `--expiration` | | `--expiration ` | Transaction expiration in ms, up to `86400000` (24h); only with `--sign-only` or `--build-only`; omitted = node default (~60s) | -| `--permission-id ` | Permission group to sign with (0=owner, 1=witness, 2–9=active); default `0` | +| `--permission-id ` | Permission group to sign with (0=owner, 1=witness, 2-9=active); default `0` | | `--wait` / `--wait-timeout ` | Poll after broadcast until confirmed/failed (cap default: config `waitTimeoutMs`, built-in 60000) | | `--password-stdin` | Master password from stdin | diff --git a/ts/docs/commands/contract/send.md b/ts/docs/commands/contract/send.md index e410d639a..287c24240 100644 --- a/ts/docs/commands/contract/send.md +++ b/ts/docs/commands/contract/send.md @@ -33,7 +33,7 @@ Requires an account. The master password (via `--password-stdin`) is needed only | `--sign-only` | Sign without broadcasting, output the signed hex; excludes `--dry-run` / `--build-only`; pairs with `--expiration` | | `--build-only` | Build only, output the **unsigned** hex; excludes `--dry-run` / `--sign-only`; pairs with `--expiration` | | `--expiration ` | Transaction expiration in ms, up to `86400000` (24h); only with `--sign-only` or `--build-only`; omitted = node default (~60s) | -| `--permission-id ` | Permission group to sign with (0=owner, 1=witness, 2–9=active); default `0` | +| `--permission-id ` | Permission group to sign with (0=owner, 1=witness, 2-9=active); default `0` | | `--wait` / `--wait-timeout ` | Poll after broadcast until confirmed/failed (cap default: config `waitTimeoutMs`, built-in 60000) | | `--password-stdin` | Master password from stdin | diff --git a/ts/docs/commands/permission/update.md b/ts/docs/commands/permission/update.md index 7a916905c..debd2a28b 100644 --- a/ts/docs/commands/permission/update.md +++ b/ts/docs/commands/permission/update.md @@ -43,8 +43,8 @@ Changing only `keys`, `threshold` or `name` needs no such deletion. | `--dry-run` | Mock receipt — fee, resulting-structure card, and warnings — matching a real submission; no signature, no broadcast, no password. Excludes `--sign-only` / `--build-only` | | `--sign-only` | Build and sign, output the signed hex without broadcasting (feed [`tx broadcast`](../tx/broadcast.md) for on-chain co-signing). Excludes `--dry-run` / `--build-only`; pairs with `--expiration` | | `--build-only` | Build only, output the **unsigned** hex (feed [`tx multisig --create`](../tx/multisig.md) for service-relayed multi-sig). Excludes `--dry-run` / `--sign-only`; pairs with `--expiration` | -| `--expiration ` | Transaction expiration in ms, up to `86400000` (24h); only with `--sign-only` or `--build-only` | -| `--permission-id ` | Permission group to sign with — changing permissions is owner-level, so normally `0` (default `0`) | +| `--expiration ` | Transaction expiration in ms, up to `86400000` (24h); only with `--sign-only` or `--build-only`; omitted = node default (~60s) | +| `--permission-id ` | Permission group to sign with (0=owner, 1=witness, 2-9=active); changing permissions normally uses `0` | | `--wait` / `--wait-timeout ` | Poll after broadcast until confirmed/failed (cap default: config `waitTimeoutMs`, built-in 60000) | | `--password-stdin` | Master password from stdin | diff --git a/ts/docs/commands/reward/withdraw.md b/ts/docs/commands/reward/withdraw.md index 4d5134ff2..cbc940746 100644 --- a/ts/docs/commands/reward/withdraw.md +++ b/ts/docs/commands/reward/withdraw.md @@ -25,7 +25,7 @@ Moves your accumulated voting rewards (plus block rewards if you are an SR) into | `--sign-only` | Sign without broadcasting, output the signed hex; excludes `--dry-run` / `--build-only`; pairs with `--expiration` | | `--build-only` | Build only, output the **unsigned** hex; excludes `--dry-run` / `--sign-only`; pairs with `--expiration` | | `--expiration ` | Transaction expiration in ms, up to `86400000` (24h); only with `--sign-only` or `--build-only`; omitted = node default (~60s) | -| `--permission-id ` | Permission group to sign with (0=owner, 1=witness, 2–9=active); default `0` | +| `--permission-id ` | Permission group to sign with (0=owner, 1=witness, 2-9=active); default `0` | | `--wait` / `--wait-timeout ` | Poll after broadcast until confirmed/failed (cap default: config `waitTimeoutMs`, built-in 60000) | | `--password-stdin` | Master password from stdin | diff --git a/ts/docs/commands/stake/cancel-unfreeze.md b/ts/docs/commands/stake/cancel-unfreeze.md index 60d990eeb..475ef2b72 100644 --- a/ts/docs/commands/stake/cancel-unfreeze.md +++ b/ts/docs/commands/stake/cancel-unfreeze.md @@ -23,7 +23,7 @@ Cancels **every** unstake still in its waiting period and rolls those amounts ba | `--sign-only` | Sign without broadcasting, output the signed hex; excludes `--dry-run` / `--build-only`; pairs with `--expiration` | | `--build-only` | Build only, output the **unsigned** hex; excludes `--dry-run` / `--sign-only`; pairs with `--expiration` | | `--expiration ` | Transaction expiration in ms, up to `86400000` (24h); only with `--sign-only` or `--build-only`; omitted = node default (~60s) | -| `--permission-id ` | Permission group to sign with (0=owner, 1=witness, 2–9=active); default `0` | +| `--permission-id ` | Permission group to sign with (0=owner, 1=witness, 2-9=active); default `0` | | `--wait` / `--wait-timeout ` | Poll after broadcast until confirmed/failed (cap default: config `waitTimeoutMs`, built-in 60000) | | `--password-stdin` | Master password from stdin | diff --git a/ts/docs/commands/stake/delegate.md b/ts/docs/commands/stake/delegate.md index 8d77a8773..33d3b59c5 100644 --- a/ts/docs/commands/stake/delegate.md +++ b/ts/docs/commands/stake/delegate.md @@ -33,7 +33,7 @@ Check how much you can still delegate with [`stake delegated`](delegated.md) (`M | `--sign-only` | Sign without broadcasting, output the signed hex; excludes `--dry-run` / `--build-only`; pairs with `--expiration` | | `--build-only` | Build only, output the **unsigned** hex; excludes `--dry-run` / `--sign-only`; pairs with `--expiration` | | `--expiration ` | Transaction expiration in ms, up to `86400000` (24h); only with `--sign-only` or `--build-only`; omitted = node default (~60s) | -| `--permission-id ` | Permission group to sign with (0=owner, 1=witness, 2–9=active); default `0` | +| `--permission-id ` | Permission group to sign with (0=owner, 1=witness, 2-9=active); default `0` | | `--wait` / `--wait-timeout ` | Poll after broadcast until confirmed/failed (cap default: config `waitTimeoutMs`, built-in 60000) | | `--password-stdin` | Master password from stdin | diff --git a/ts/docs/commands/stake/freeze.md b/ts/docs/commands/stake/freeze.md index 4071420ef..1118ba65e 100644 --- a/ts/docs/commands/stake/freeze.md +++ b/ts/docs/commands/stake/freeze.md @@ -27,7 +27,7 @@ Amount is in SUN (1 TRX = 1,000,000 SUN). Staked TRX stays yours; to get it back | `--sign-only` | Sign without broadcasting, output the signed hex; excludes `--dry-run` / `--build-only`; pairs with `--expiration` | | `--build-only` | Build only, output the **unsigned** hex; excludes `--dry-run` / `--sign-only`; pairs with `--expiration` | | `--expiration ` | Transaction expiration in ms, up to `86400000` (24h); only with `--sign-only` or `--build-only`; omitted = node default (~60s) | -| `--permission-id ` | Permission group to sign with (0=owner, 1=witness, 2–9=active); default `0` | +| `--permission-id ` | Permission group to sign with (0=owner, 1=witness, 2-9=active); default `0` | | `--wait` / `--wait-timeout ` | Poll after broadcast until confirmed/failed (cap default: config `waitTimeoutMs`, built-in 60000) | | `--password-stdin` | Master password from stdin | diff --git a/ts/docs/commands/stake/undelegate.md b/ts/docs/commands/stake/undelegate.md index aab2f0dbc..5e313addb 100644 --- a/ts/docs/commands/stake/undelegate.md +++ b/ts/docs/commands/stake/undelegate.md @@ -29,7 +29,7 @@ Reclaiming is immediate (no waiting period — the TRX was staked all along, onl | `--sign-only` | Sign without broadcasting, output the signed hex; excludes `--dry-run` / `--build-only`; pairs with `--expiration` | | `--build-only` | Build only, output the **unsigned** hex; excludes `--dry-run` / `--sign-only`; pairs with `--expiration` | | `--expiration ` | Transaction expiration in ms, up to `86400000` (24h); only with `--sign-only` or `--build-only`; omitted = node default (~60s) | -| `--permission-id ` | Permission group to sign with (0=owner, 1=witness, 2–9=active); default `0` | +| `--permission-id ` | Permission group to sign with (0=owner, 1=witness, 2-9=active); default `0` | | `--wait` / `--wait-timeout ` | Poll after broadcast until confirmed/failed (cap default: config `waitTimeoutMs`, built-in 60000) | | `--password-stdin` | Master password from stdin | diff --git a/ts/docs/commands/stake/unfreeze.md b/ts/docs/commands/stake/unfreeze.md index f24b150fd..43ad8cfc5 100644 --- a/ts/docs/commands/stake/unfreeze.md +++ b/ts/docs/commands/stake/unfreeze.md @@ -27,7 +27,7 @@ Stake 2.0 allows at most **32 pending unstakes** per account at a time; check re | `--sign-only` | Sign without broadcasting, output the signed hex; excludes `--dry-run` / `--build-only`; pairs with `--expiration` | | `--build-only` | Build only, output the **unsigned** hex; excludes `--dry-run` / `--sign-only`; pairs with `--expiration` | | `--expiration ` | Transaction expiration in ms, up to `86400000` (24h); only with `--sign-only` or `--build-only`; omitted = node default (~60s) | -| `--permission-id ` | Permission group to sign with (0=owner, 1=witness, 2–9=active); default `0` | +| `--permission-id ` | Permission group to sign with (0=owner, 1=witness, 2-9=active); default `0` | | `--wait` / `--wait-timeout ` | Poll after broadcast until confirmed/failed (cap default: config `waitTimeoutMs`, built-in 60000) | | `--password-stdin` | Master password from stdin | diff --git a/ts/docs/commands/stake/withdraw.md b/ts/docs/commands/stake/withdraw.md index bff901a40..3cff26c4a 100644 --- a/ts/docs/commands/stake/withdraw.md +++ b/ts/docs/commands/stake/withdraw.md @@ -25,7 +25,7 @@ Withdrawing also frees up unstake slots (max 32 pending unstakes per account). | `--sign-only` | Sign without broadcasting, output the signed hex; excludes `--dry-run` / `--build-only`; pairs with `--expiration` | | `--build-only` | Build only, output the **unsigned** hex; excludes `--dry-run` / `--sign-only`; pairs with `--expiration` | | `--expiration ` | Transaction expiration in ms, up to `86400000` (24h); only with `--sign-only` or `--build-only`; omitted = node default (~60s) | -| `--permission-id ` | Permission group to sign with (0=owner, 1=witness, 2–9=active); default `0` | +| `--permission-id ` | Permission group to sign with (0=owner, 1=witness, 2-9=active); default `0` | | `--wait` / `--wait-timeout ` | Poll after broadcast until confirmed/failed (cap default: config `waitTimeoutMs`, built-in 60000) | | `--password-stdin` | Master password from stdin | diff --git a/ts/docs/commands/tx/send.md b/ts/docs/commands/tx/send.md index 7087e1697..c7338ae41 100644 --- a/ts/docs/commands/tx/send.md +++ b/ts/docs/commands/tx/send.md @@ -42,8 +42,8 @@ Requires an account and the master password via `--password-stdin` — signing c | `--dry-run` | Build and estimate only; excludes `--sign-only` / `--build-only` | | `--sign-only` | Sign without broadcasting, output the signed hex; excludes `--dry-run` / `--build-only`; pairs with `--expiration` | | `--build-only` | Build only, output the **unsigned** hex; excludes `--dry-run` / `--sign-only`; pairs with `--expiration` | -| `--expiration ` | Transaction expiration in ms, up to `86400000` (24h); only with `--sign-only` or `--build-only` | -| `--permission-id ` | Permission group to sign with (0=owner, 1=witness, 2–9=active); default `0` | +| `--expiration ` | Transaction expiration in ms, up to `86400000` (24h); only with `--sign-only` or `--build-only`; omitted = node default (~60s) | +| `--permission-id ` | Permission group to sign with (0=owner, 1=witness, 2-9=active); default `0` | | `--wait` / `--wait-timeout ` | Poll after broadcast until confirmed/failed (cap default 60000; on cap returns the submitted receipt) | | `--password-stdin` | Master password from stdin | diff --git a/ts/docs/commands/vote/cast.md b/ts/docs/commands/vote/cast.md index 05abfbece..5c33138f9 100644 --- a/ts/docs/commands/vote/cast.md +++ b/ts/docs/commands/vote/cast.md @@ -31,7 +31,7 @@ Votes take effect at the next maintenance cycle (~6 h). Each vote uses 1 TP (it | `--sign-only` | Sign without broadcasting, output the signed hex; excludes `--dry-run` / `--build-only`; pairs with `--expiration` | | `--build-only` | Build only, output the **unsigned** hex; excludes `--dry-run` / `--sign-only`; pairs with `--expiration` | | `--expiration ` | Transaction expiration in ms, up to `86400000` (24h); only with `--sign-only` or `--build-only`; omitted = node default (~60s) | -| `--permission-id ` | Permission group to sign with (0=owner, 1=witness, 2–9=active); default `0` | +| `--permission-id ` | Permission group to sign with (0=owner, 1=witness, 2-9=active); default `0` | | `--wait` / `--wait-timeout ` | Poll after broadcast until confirmed/failed (cap default: config `waitTimeoutMs`, built-in 60000) | | `--password-stdin` | Master password from stdin (fd 0) | diff --git a/ts/src/adapters/inbound/cli/input/prompt/index.ts b/ts/src/adapters/inbound/cli/input/prompt/index.ts index 6b0a02975..f5166c3f3 100644 --- a/ts/src/adapters/inbound/cli/input/prompt/index.ts +++ b/ts/src/adapters/inbound/cli/input/prompt/index.ts @@ -131,6 +131,10 @@ export class TtyBackend implements PromptBackend { #pendingKey?: (key: KeyEvent) => void; #keyListener?: (s: string, key: KeyEvent) => void; constructor() { + if (process.env.WALLET_CLI_NO_TTY === "1") { + this.#tty = false; + return; + } // Probe for a controlling terminal without holding the fd; the real stream opens on first prompt. try { closeSync(openSync("/dev/tty", "r")); diff --git a/ts/src/adapters/inbound/cli/input/prompt/prompter.test.ts b/ts/src/adapters/inbound/cli/input/prompt/prompter.test.ts index 197da86d6..095dbe62a 100644 --- a/ts/src/adapters/inbound/cli/input/prompt/prompter.test.ts +++ b/ts/src/adapters/inbound/cli/input/prompt/prompter.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from "vitest"; -import { Prompter, type PromptBackend, type KeyEvent } from "./index.js"; +import { createPrompter, Prompter, type PromptBackend, type KeyEvent } from "./index.js"; class FakeBackend implements PromptBackend { out = ""; @@ -25,6 +25,19 @@ describe("Prompter.setInteractive", () => { }); }); +describe("createPrompter", () => { + it("can force non-interactive mode for subprocess tests", () => { + const previous = process.env.WALLET_CLI_NO_TTY; + process.env.WALLET_CLI_NO_TTY = "1"; + try { + expect(createPrompter().isTTY()).toBe(false); + } finally { + if (previous === undefined) delete process.env.WALLET_CLI_NO_TTY; + else process.env.WALLET_CLI_NO_TTY = previous; + } + }); +}); + describe("Prompter.text", () => { it("re-prompts until validate passes", async () => { const be = new FakeBackend(["", " ", "ok"]); diff --git a/ts/src/adapters/inbound/cli/shell/positional-contract.test.ts b/ts/src/adapters/inbound/cli/shell/positional-contract.test.ts index 0b8215793..5a010c95b 100644 --- a/ts/src/adapters/inbound/cli/shell/positional-contract.test.ts +++ b/ts/src/adapters/inbound/cli/shell/positional-contract.test.ts @@ -5,6 +5,8 @@ import { tmpdir } from "node:os"; import { buildCli, type ShellOptions } from "./index.js"; import { isChainCommand, type SessionRef } from "../contracts/index.js"; import { composeCliRuntime } from "../../../../bootstrap/composition.js"; +import { Prompter } from "../input/prompt/index.js"; +import { SecretResolver } from "../input/secret/index.js"; /** * The other shell tests drive synthetic command definitions, which proves the mechanism but not @@ -31,11 +33,22 @@ describe("every registered positional command rejects its -- spelling", ( // fresh per invocation: StreamManager permits one result emission, so a runtime cannot be shared // across commands that actually run. function newRuntime() { - return composeCliRuntime({ + const runtime = composeCliRuntime({ globals: { output: "json", verbose: false }, secretPaths: {}, startedAt: Date.now(), }); + const prompter = new Prompter({ + isTTY: () => false, + async question() { return ""; }, + async readKey() { return { name: "return" }; }, + write() {}, + beginRaw() {}, + endRaw() {}, + }); + runtime.deps.prompter = prompter; + runtime.deps.secrets = new SecretResolver(runtime.streams, {}, prompter); + return runtime; } function shellOpts(): ShellOptions { diff --git a/ts/src/adapters/outbound/ledger/index.test.ts b/ts/src/adapters/outbound/ledger/index.test.ts index b57c16175..daab168d2 100644 --- a/ts/src/adapters/outbound/ledger/index.test.ts +++ b/ts/src/adapters/outbound/ledger/index.test.ts @@ -10,7 +10,7 @@ const { closeSpy, tip712Calls, failures } = vi.hoisted(() => ({ tip712Calls: [] as Array<{ path: string; domainHash: string; messageHash: string }>, failures: { tip712: undefined as Error | undefined, tip712Hang: false }, })); -vi.mock("@ledgerhq/hw-transport-node-hid", () => ({ +vi.mock("@ledgerhq/hw-transport-node-hid-noevents", () => ({ default: { open: async () => ({ close: closeSpy }) }, })); // Every device APDU never resolves — models an on-device prompt that is never tapped. diff --git a/ts/src/adapters/outbound/ledger/index.ts b/ts/src/adapters/outbound/ledger/index.ts index 0ef328447..7715314ee 100644 --- a/ts/src/adapters/outbound/ledger/index.ts +++ b/ts/src/adapters/outbound/ledger/index.ts @@ -61,7 +61,7 @@ async function openTransport(): Promise<{ transport: unknown; close: () => Promi }); return { transport, close: () => transport.close() }; } - const Hid = unwrap(await import("@ledgerhq/hw-transport-node-hid")); + const Hid = unwrap(await import("@ledgerhq/hw-transport-node-hid-noevents")); const transport = await Hid.open(""); return { transport, close: () => transport.close() }; } diff --git a/ts/test/contract-deploy.test.ts b/ts/test/contract-deploy.test.ts index cc385e6d4..66ee0df78 100644 --- a/ts/test/contract-deploy.test.ts +++ b/ts/test/contract-deploy.test.ts @@ -20,7 +20,7 @@ import { AtomicFileStore } from "../src/adapters/outbound/persistence/fs/index.j // RUN_LIVE_BROADCAST=1 → actually deploy + confirm on Nile (spends testnet TRX) const HERE = dirname(fileURLToPath(import.meta.url)); -const TSX = join(process.cwd(), "node_modules", ".bin", "tsx"); +const NODE = process.execPath; const ENTRY = join(process.cwd(), "src", "index.ts"); const PW = "testpw123A"; @@ -62,11 +62,11 @@ function deploy( ]; if (opts.dryRun) local.push("--dry-run"); local.push("--password-stdin"); - const r = spawnSync(TSX, [ENTRY, ...globals, ...local], { + const r = spawnSync(NODE, ["--import", "tsx", ENTRY, ...globals, ...local], { input: PW + "\n", encoding: "utf8", - env: { ...process.env, WALLET_CLI_HOME: HOME, NO_COLOR: "1" }, - timeout: opts.timeoutMs ?? 30_000, + env: { ...process.env, WALLET_CLI_HOME: HOME, NO_COLOR: "1", WALLET_CLI_NO_TTY: "1" }, + timeout: opts.timeoutMs ?? 18_000, }); return JSON.parse(r.stdout); } diff --git a/ts/test/golden.test.ts b/ts/test/golden.test.ts index 03870d284..ca8ded4f7 100644 --- a/ts/test/golden.test.ts +++ b/ts/test/golden.test.ts @@ -8,7 +8,7 @@ import { TokenBook } from "../src/adapters/outbound/tokenbook/index.js" import { AtomicFileStore } from "../src/adapters/outbound/persistence/fs/index.js" import type { TokenEntry } from "../src/domain/types/index.js" -const TSX = join(process.cwd(), "node_modules", ".bin", "tsx") +const NODE = process.execPath const ENTRY = join(process.cwd(), "src", "index.ts") const MNEMONIC = "test test test test test test test test test test test junk" const TRON1 = "TLa2f6VPqDgRE67v1736s7bJ8Ray5wYjU7" @@ -23,7 +23,7 @@ beforeEach(() => { // interactive so it can't run as a black-box subprocess — wallet setup uses seedWallet() to write // the keystore in-process instead. No MASTER_PASSWORD env. password:null → no source (auth_required). function run(args: string[], opts: { input?: string; password?: string | null } = {}) { - const env: Record = { ...process.env, WALLET_CLI_HOME: HOME } as Record + const env: Record = { ...process.env, WALLET_CLI_HOME: HOME, WALLET_CLI_NO_TTY: "1" } as Record delete env.MASTER_PASSWORD const finalArgs = [...args] let stdin = opts.input @@ -31,9 +31,9 @@ function run(args: string[], opts: { input?: string; password?: string | null } finalArgs.push("--password-stdin") stdin = (opts.password ?? DEFAULT_PW) + "\n" } - // 25s < the suite's 30s testTimeout: a genuinely hung subprocess errors here with a clear + // 18s < the suite's 20s testTimeout: a genuinely hung subprocess errors here with a clear // signal instead of silently eating the whole test budget. - const r = spawnSync(TSX, [ENTRY, ...finalArgs], { input: stdin, encoding: "utf8", env, timeout: 25_000 }) + const r = spawnSync(NODE, ["--import", "tsx", ENTRY, ...finalArgs], { input: stdin, encoding: "utf8", env, timeout: 18_000 }) let json: any try { json = JSON.parse(r.stdout) @@ -235,7 +235,7 @@ describe("golden CLI — wallet lifecycle (shared identity)", () => { const again = run(["--output", "json", "backup", "main", "--out", out]) expect(again.status).toBe(2) expect(again.json.error.code).toBe("output_exists") - }, 15000) // seed encrypt + two backup decrypts run scrypt 3× → exceeds vitest's 5s default + }, 20_000) // seed encrypt + two backup decrypts run scrypt 3× → exceeds vitest's 5s default it("supports root-level use and backup account commands", () => { seedWallet() diff --git a/ts/vitest.config.ts b/ts/vitest.config.ts index d7334be5b..9c8dafd18 100644 --- a/ts/vitest.config.ts +++ b/ts/vitest.config.ts @@ -9,19 +9,21 @@ export default defineConfig({ name: "unit", environment: "node", include: ["src/**/*.test.ts"], + testTimeout: 20_000, + hookTimeout: 20_000, }, }, { - // Golden tests spawn a fresh `tsx src/index.ts` per case, which cold-transpiles the + // Golden tests spawn a fresh `node --import tsx src/index.ts` per case, which cold-transpiles the // whole CLI import graph each time. Under parallel CPU load a single spawn can take far // longer than vitest's default 5s testTimeout, causing intermittent timeout failures. - // Give this suite generous timeouts so transient slowness doesn't flake the run. + // Keep this suite above the child-process guard so hangs fail with subprocess details. test: { name: "golden", environment: "node", include: ["test/**/*.test.ts"], - testTimeout: 30_000, - hookTimeout: 30_000, + testTimeout: 20_000, + hookTimeout: 20_000, }, }, ],