diff --git a/deno.lock b/deno.lock index 910c8ee4d..e58285b97 100644 --- a/deno.lock +++ b/deno.lock @@ -57,7 +57,7 @@ "npm:@coderspirit/nominal@^4.1.1": "4.1.1_typescript@5.6.3", "npm:@dcspark/carp-client@^3.3.0": "3.3.0", "npm:@dcspark/cip34-js@3.0.1": "3.0.1", - "npm:@deno/vite-plugin@^1.0.4": "1.0.6_vite@7.1.3__picomatch@4.0.3", + "npm:@deno/vite-plugin@^1.0.4": "1.0.6_vite@7.1.3__picomatch@4.0.3_@types+node@24.2.0", "npm:@docusaurus/core@3.8.1": "3.8.1_@mdx-js+react@3.1.1__@types+react@18.3.1__react@18.3.1_react@18.3.1_react-dom@18.3.1__react@18.3.1_@types+react@18.3.1_webpack@5.104.1__acorn@8.15.0_react-router@5.3.4__react@18.3.1_typescript@5.6.3", "npm:@docusaurus/module-type-aliases@3.8.1": "3.8.1_react@18.3.1_react-dom@18.3.1__react@18.3.1", "npm:@docusaurus/plugin-client-redirects@3.8.1": "3.8.1_react@18.3.1_react-dom@18.3.1__react@18.3.1_@mdx-js+react@3.1.1__@types+react@18.3.1__react@18.3.1_@types+react@18.3.1_typescript@5.6.3", @@ -134,13 +134,14 @@ "npm:@sinclair/typebox@0.34.41": "0.34.41", "npm:@subsquid/ss58-codec@^1.2.3": "1.2.3", "npm:@txpipe/dolos@0.19.1": "0.19.1", + "npm:@types/node@*": "24.2.0", "npm:@types/react@18.3.1": "18.3.1", "npm:@types/react@19.1.0": "19.1.0", "npm:@types/react@19.1.2": "19.1.2", "npm:@types/semver@7.7.1": "7.7.1", "npm:@utxorpc/sdk@0.8": "0.8.0_@connectrpc+connect@1.4.0__@bufbuild+protobuf@1.10.1", "npm:@utxorpc/spec@~0.18.1": "0.18.1", - "npm:@vitejs/plugin-react@^4.4.1": "4.7.0_vite@7.1.3__picomatch@4.0.3_@babel+core@7.29.0", + "npm:@vitejs/plugin-react@^4.4.1": "4.7.0_vite@7.1.3__picomatch@4.0.3_@babel+core@7.29.0_@types+node@24.2.0", "npm:@wagmi/cli@2.3.1": "2.3.1_typescript@5.6.3_zod@3.25.76_esbuild@0.25.12_picomatch@3.0.1", "npm:@xhmikosr/bin-wrapper@5": "5.0.1", "npm:@xhmikosr/bin-wrapper@^13.2.0": "13.2.0", @@ -229,13 +230,13 @@ "npm:viem@2.23.10": "2.23.10_typescript@5.6.3_ws@8.18.1", "npm:viem@2.37.3": "2.37.3_typescript@5.6.3_ws@8.18.3__bufferutil@4.1.0__utf-8-validate@5.0.10", "npm:viem@^2.21.3": "2.45.2_typescript@5.6.3_ws@8.18.3__bufferutil@4.1.0__utf-8-validate@5.0.10", - "npm:vite-plugin-node-stdlib-browser@*": "0.2.1_node-stdlib-browser@1.3.1_vite@7.1.3__picomatch@4.0.3", - "npm:vite-plugin-static-copy@^3.1.1": "3.2.0_vite@7.1.3__picomatch@4.0.3", - "npm:vite-plugin-top-level-await@^1.6.0": "1.6.0_vite@7.1.3__picomatch@4.0.3", - "npm:vite-plugin-wasm@^3.5.0": "3.5.0_vite@7.1.3__picomatch@4.0.3", - "npm:vite@*": "7.1.3_picomatch@4.0.3", - "npm:vite@7.1.3": "7.1.3_picomatch@4.0.3", - "npm:vitest@^3.2.4": "3.2.4_vite@7.1.3__picomatch@4.0.3", + "npm:vite-plugin-node-stdlib-browser@*": "0.2.1_node-stdlib-browser@1.3.1_vite@7.1.3__picomatch@4.0.3_@types+node@24.2.0", + "npm:vite-plugin-static-copy@^3.1.1": "3.2.0_vite@7.1.3__picomatch@4.0.3_@types+node@24.2.0", + "npm:vite-plugin-top-level-await@^1.6.0": "1.6.0_vite@7.1.3__picomatch@4.0.3_@types+node@24.2.0", + "npm:vite-plugin-wasm@^3.5.0": "3.5.0_vite@7.1.3__picomatch@4.0.3_@types+node@24.2.0", + "npm:vite@*": "7.1.3_picomatch@4.0.3_@types+node@24.2.0", + "npm:vite@7.1.3": "7.1.3_picomatch@4.0.3_@types+node@24.2.0", + "npm:vitest@^3.2.4": "3.2.4_vite@7.1.3__picomatch@4.0.3_@types+node@24.2.0", "npm:wagmi@^2.16.9": "2.19.5_@tanstack+react-query@5.90.20__react@18.3.1_react@18.3.1_typescript@5.6.3_viem@2.23.10__typescript@5.6.3__ws@8.18.1_@wagmi+core@2.22.1__typescript@5.6.3__viem@2.23.10___typescript@5.6.3___ws@8.18.1__@types+react@18.3.1__react@18.3.1__use-sync-external-store@1.4.0___react@18.3.1_@types+react@18.3.1_use-sync-external-store@1.4.0__react@18.3.1", "npm:wait-on@8.0.3": "8.0.3", "npm:web3-utils@^4.3.3": "4.3.3", @@ -2187,7 +2188,13 @@ "@deno/vite-plugin@1.0.6_vite@7.1.3__picomatch@4.0.3": { "integrity": "sha512-Sh5XqvFuKAwjARTesi0n6xRpEXm1V0UeqKh+SxIrexCofxOaieNDMqXZD02RiZCg0mrJ43V8eCMuVrDfq6mLmg==", "dependencies": [ - "vite" + "vite@7.1.3_picomatch@4.0.3" + ] + }, + "@deno/vite-plugin@1.0.6_vite@7.1.3__picomatch@4.0.3_@types+node@24.2.0": { + "integrity": "sha512-Sh5XqvFuKAwjARTesi0n6xRpEXm1V0UeqKh+SxIrexCofxOaieNDMqXZD02RiZCg0mrJ43V8eCMuVrDfq6mLmg==", + "dependencies": [ + "vite@7.1.3_picomatch@4.0.3_@types+node@24.2.0" ] }, "@discoveryjs/json-ext@0.5.7": { @@ -8091,6 +8098,12 @@ "undici-types@6.19.8" ] }, + "@types/node@24.2.0": { + "integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==", + "dependencies": [ + "undici-types@7.10.0" + ] + }, "@types/object-inspect@1.13.0": { "integrity": "sha512-lwGTVESDDV+XsQ1pH4UifpJ1f7OtXzQ6QBOX2Afq2bM/T3oOt8hF6exJMjjIjtEWeAN2YAo25J7HxWh97CCz9w==" }, @@ -8308,7 +8321,19 @@ "@rolldown/pluginutils", "@types/babel__core", "react-refresh", - "vite" + "vite@7.1.3_picomatch@4.0.3" + ] + }, + "@vitejs/plugin-react@4.7.0_vite@7.1.3__picomatch@4.0.3_@babel+core@7.29.0_@types+node@24.2.0": { + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dependencies": [ + "@babel/core", + "@babel/plugin-transform-react-jsx-self", + "@babel/plugin-transform-react-jsx-source", + "@rolldown/pluginutils", + "@types/babel__core", + "react-refresh", + "vite@7.1.3_picomatch@4.0.3_@types+node@24.2.0" ] }, "@vitest/expect@3.2.4": { @@ -8327,10 +8352,22 @@ "@vitest/spy", "estree-walker@3.0.3", "magic-string", - "vite" + "vite@7.1.3_picomatch@4.0.3" + ], + "optionalPeers": [ + "vite@7.1.3_picomatch@4.0.3" + ] + }, + "@vitest/mocker@3.2.4_vite@7.1.3__picomatch@4.0.3_@types+node@24.2.0": { + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dependencies": [ + "@vitest/spy", + "estree-walker@3.0.3", + "magic-string", + "vite@7.1.3_picomatch@4.0.3_@types+node@24.2.0" ], "optionalPeers": [ - "vite" + "vite@7.1.3_picomatch@4.0.3_@types+node@24.2.0" ] }, "@vitest/pretty-format@3.2.4": { @@ -18750,7 +18787,8 @@ "minipass@7.1.2", "minizlib@3.1.0", "yallist@5.0.0" - ] + ], + "deprecated": true }, "terser-webpack-plugin@5.3.16_webpack@5.104.1__acorn@8.15.0": { "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", @@ -19107,6 +19145,9 @@ "undici-types@6.19.8": { "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" }, + "undici-types@7.10.0": { + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==" + }, "undici-types@7.21.0": { "integrity": "sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ==" }, @@ -19558,7 +19599,18 @@ "debug@4.4.3", "es-module-lexer@1.7.0", "pathe@2.0.3", - "vite" + "vite@7.1.3_picomatch@4.0.3" + ], + "bin": true + }, + "vite-node@3.2.4_@types+node@24.2.0": { + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dependencies": [ + "cac", + "debug@4.4.3", + "es-module-lexer@1.7.0", + "pathe@2.0.3", + "vite@7.1.3_picomatch@4.0.3_@types+node@24.2.0" ], "bin": true }, @@ -19567,7 +19619,15 @@ "dependencies": [ "@rollup/plugin-inject", "node-stdlib-browser", - "vite" + "vite@7.1.3_picomatch@4.0.3" + ] + }, + "vite-plugin-node-stdlib-browser@0.2.1_node-stdlib-browser@1.3.1_vite@7.1.3__picomatch@4.0.3_@types+node@24.2.0": { + "integrity": "sha512-6u2i613Dkqj5KaTNIrnZvE6y3/awWAp0S5TjucTvGxdhetftB1Mgvblc+nwYzlw6sntPlac8UOC7ttXNh+LZKA==", + "dependencies": [ + "@rollup/plugin-inject", + "node-stdlib-browser", + "vite@7.1.3_picomatch@4.0.3_@types+node@24.2.0" ] }, "vite-plugin-static-copy@3.2.0_vite@7.1.3__picomatch@4.0.3": { @@ -19577,7 +19637,17 @@ "p-map@7.0.4", "picocolors", "tinyglobby", - "vite" + "vite@7.1.3_picomatch@4.0.3" + ] + }, + "vite-plugin-static-copy@3.2.0_vite@7.1.3__picomatch@4.0.3_@types+node@24.2.0": { + "integrity": "sha512-g2k9z8B/1Bx7D4wnFjPLx9dyYGrqWMLTpwTtPHhcU+ElNZP2O4+4OsyaficiDClus0dzVhdGvoGFYMJxoXZ12Q==", + "dependencies": [ + "chokidar@3.6.0", + "p-map@7.0.4", + "picocolors", + "tinyglobby", + "vite@7.1.3_picomatch@4.0.3_@types+node@24.2.0" ] }, "vite-plugin-top-level-await@1.6.0_vite@7.1.3__picomatch@4.0.3": { @@ -19587,13 +19657,29 @@ "@swc/core", "@swc/wasm", "uuid@10.0.0", - "vite" + "vite@7.1.3_picomatch@4.0.3" + ] + }, + "vite-plugin-top-level-await@1.6.0_vite@7.1.3__picomatch@4.0.3_@types+node@24.2.0": { + "integrity": "sha512-bNhUreLamTIkoulCR9aDXbTbhLk6n1YE8NJUTTxl5RYskNRtzOR0ASzSjBVRtNdjIfngDXo11qOsybGLNsrdww==", + "dependencies": [ + "@rollup/plugin-virtual", + "@swc/core", + "@swc/wasm", + "uuid@10.0.0", + "vite@7.1.3_picomatch@4.0.3_@types+node@24.2.0" ] }, "vite-plugin-wasm@3.5.0_vite@7.1.3__picomatch@4.0.3": { "integrity": "sha512-X5VWgCnqiQEGb+omhlBVsvTfxikKtoOgAzQ95+BZ8gQ+VfMHIjSHr0wyvXFQCa0eKQ0fKyaL0kWcEnYqBac4lQ==", "dependencies": [ - "vite" + "vite@7.1.3_picomatch@4.0.3" + ] + }, + "vite-plugin-wasm@3.5.0_vite@7.1.3__picomatch@4.0.3_@types+node@24.2.0": { + "integrity": "sha512-X5VWgCnqiQEGb+omhlBVsvTfxikKtoOgAzQ95+BZ8gQ+VfMHIjSHr0wyvXFQCa0eKQ0fKyaL0kWcEnYqBac4lQ==", + "dependencies": [ + "vite@7.1.3_picomatch@4.0.3_@types+node@24.2.0" ] }, "vite@7.1.3_picomatch@4.0.3": { @@ -19611,12 +19697,61 @@ ], "bin": true }, + "vite@7.1.3_picomatch@4.0.3_@types+node@24.2.0": { + "integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==", + "dependencies": [ + "@types/node@24.2.0", + "esbuild@0.25.12", + "fdir@6.5.0_picomatch@4.0.3", + "picomatch@4.0.3", + "postcss", + "rollup", + "tinyglobby" + ], + "optionalDependencies": [ + "fsevents" + ], + "optionalPeers": [ + "@types/node@24.2.0" + ], + "bin": true + }, "vitest@3.2.4_vite@7.1.3__picomatch@4.0.3": { "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dependencies": [ "@types/chai", "@vitest/expect", - "@vitest/mocker", + "@vitest/mocker@3.2.4_vite@7.1.3__picomatch@4.0.3", + "@vitest/pretty-format", + "@vitest/runner", + "@vitest/snapshot", + "@vitest/spy", + "@vitest/utils", + "chai", + "debug@4.4.3", + "expect-type", + "magic-string", + "pathe@2.0.3", + "picomatch@4.0.3", + "std-env", + "tinybench", + "tinyexec@0.3.2", + "tinyglobby", + "tinypool", + "tinyrainbow", + "vite@7.1.3_picomatch@4.0.3", + "vite-node@3.2.4", + "why-is-node-running" + ], + "bin": true + }, + "vitest@3.2.4_vite@7.1.3__picomatch@4.0.3_@types+node@24.2.0": { + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dependencies": [ + "@types/chai", + "@types/node@24.2.0", + "@vitest/expect", + "@vitest/mocker@3.2.4_vite@7.1.3__picomatch@4.0.3_@types+node@24.2.0", "@vitest/pretty-format", "@vitest/runner", "@vitest/snapshot", @@ -19634,10 +19769,13 @@ "tinyglobby", "tinypool", "tinyrainbow", - "vite", - "vite-node", + "vite@7.1.3_picomatch@4.0.3_@types+node@24.2.0", + "vite-node@3.2.4_@types+node@24.2.0", "why-is-node-running" ], + "optionalPeers": [ + "@types/node@24.2.0" + ], "bin": true }, "vlq@2.0.4": { @@ -20582,32 +20720,6 @@ "npm:viem@2.37.3" ] }, - "e2e/e2e-offer-files/packages/midnight": { - "dependencies": [ - "jsr:@std/fs@*", - "jsr:@std/path@*", - "npm:@midnight-ntwrk/compact-js@2.4.0", - "npm:@midnight-ntwrk/compact-runtime@0.14.0", - "npm:@midnight-ntwrk/ledger-v7@7.0.0", - "npm:@midnight-ntwrk/midnight-js-contracts@3.0.0", - "npm:@midnight-ntwrk/midnight-js-http-client-proof-provider@3.0.0", - "npm:@midnight-ntwrk/midnight-js-indexer-public-data-provider@3.0.0", - "npm:@midnight-ntwrk/midnight-js-level-private-state-provider@3.0.0", - "npm:@midnight-ntwrk/midnight-js-network-id@3.0.0", - "npm:@midnight-ntwrk/midnight-js-node-zk-config-provider@3.0.0", - "npm:@midnight-ntwrk/midnight-js-types@3.0.0", - "npm:@midnight-ntwrk/midnight-js-utils@3.0.0", - "npm:@midnight-ntwrk/wallet-sdk-abstractions@1.0.0", - "npm:@midnight-ntwrk/wallet-sdk-address-format@3.0.0", - "npm:@midnight-ntwrk/wallet-sdk-dust-wallet@1.0.0", - "npm:@midnight-ntwrk/wallet-sdk-facade@1.0.0", - "npm:@midnight-ntwrk/wallet-sdk-hd@3.0.0", - "npm:@midnight-ntwrk/wallet-sdk-shielded@1.0.0", - "npm:@midnight-ntwrk/wallet-sdk-unshielded-wallet@1.0.0", - "npm:rxjs@^7.8.2", - "npm:ws@*" - ] - }, "e2e/e2e-wallets": { "dependencies": [ "jsr:@oak/oak@^17.1.4", @@ -20802,6 +20914,13 @@ ] } }, + "packages/binaries/compactc": { + "packageJson": { + "dependencies": [ + "npm:@xhmikosr/bin-wrapper@5" + ] + } + }, "packages/binaries/grafana-alloy": { "packageJson": { "dependencies": [ @@ -21101,6 +21220,7 @@ "npm:@coderspirit/nominal@^4.1.1", "npm:@foxglove/crc@^1.0.1", "npm:@metamask/providers@^22.1.0", + "npm:@midnight-ntwrk/dapp-connector-api@4.0.0", "npm:@perawallet/connect@^1.4.2", "npm:@polkadot/extension-dapp@~0.58.10", "npm:@polkadot/extension-inject@~0.58.10", @@ -21124,25 +21244,13 @@ "npm:mina-signer@^3.0.7", "npm:mqtt-pattern@^2.1.0", "npm:mqtt@^5.14.0", + "npm:semver@^7.7.3", "npm:viem@2.37.3", "npm:web3-utils@^4.3.3", "npm:web3@^4.16.0" ] } }, - "packages/node-sdk/batcher": { - "dependencies": [ - "jsr:@std/cli@^1.0.24", - "npm:@fastify/cors@^11.0.1", - "npm:@fastify/swagger-ui@^5.2.3", - "npm:@fastify/swagger@^9.5.1", - "npm:@sinclair/typebox@0.34.41", - "npm:assert-never@^1.4.0", - "npm:effection@^3.5.0", - "npm:fastify@^5.4.0", - "npm:viem@2.37.3" - ] - }, "packages/node-sdk/db": { "dependencies": [ "npm:@pgtyped/parser@2.4.2", diff --git a/e2e/client/batcher/adapter-bitcoin.ts b/e2e/client/batcher/adapter-bitcoin.ts index 889b1992d..095e9942d 100644 --- a/e2e/client/batcher/adapter-bitcoin.ts +++ b/e2e/client/batcher/adapter-bitcoin.ts @@ -4,8 +4,9 @@ import { buildBitcoinSignatureMessage, BitcoinAdapter } from "@effectstream/batc import * as bitcoin from "bitcoinjs-lib"; import * as ecpair from "ecpair"; import * as tinysecp from "tiny-secp256k1"; +import { getEnv } from "@effectstream/utils/runtime"; -const isEnvTrue = (key: string) => ["true", "1", "yes", "y"].includes((Deno.env.get(key) || "").toLowerCase()); +const isEnvTrue = (key: string) => ["true", "1", "yes", "y"].includes((getEnv(key) || "").toLowerCase()); const bitcoin_enabled = !isEnvTrue("DISABLE_BITCOIN"); diff --git a/e2e/client/batcher/adapter-counter.ts b/e2e/client/batcher/adapter-counter.ts index 02e39ea3b..680d0dd24 100644 --- a/e2e/client/batcher/adapter-counter.ts +++ b/e2e/client/batcher/adapter-counter.ts @@ -10,8 +10,9 @@ const COUNTER_SYNC_PROTOCOL = "parallelEvmRPC_fast"; const COUNTER_ADDRESS = contractAddressesEvmMain()["chain31337"][ "CounterModule#Counter" ] as `0x${string}`; +import { getEnv } from "@effectstream/utils/runtime"; const COUNTER_PRIVATE_KEY = - (Deno.env.get("COUNTER_BATCHER_PRIVATE_KEY") ?? + (getEnv("COUNTER_BATCHER_PRIVATE_KEY") ?? "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d") as `0x${string}`; const counterConfig: EvmContractAdapterConfig = { diff --git a/e2e/client/batcher/adapter-midnight-balancing.ts b/e2e/client/batcher/adapter-midnight-balancing.ts index 0ca0e0a6f..1cf34a759 100644 --- a/e2e/client/batcher/adapter-midnight-balancing.ts +++ b/e2e/client/batcher/adapter-midnight-balancing.ts @@ -1,5 +1,6 @@ import { MidnightBalancingAdapter } from "@effectstream/batcher"; import { dirname, resolve } from "@std/path"; +import { ENV } from "@effectstream/utils/node-env"; const currentDir = dirname(new URL(import.meta.url).pathname); @@ -12,10 +13,9 @@ import { midnightNetworkConfig } from "@effectstream/midnight-contracts/midnight const DEFAULT_WALLET_SEED = midnightNetworkConfig.walletSeed!; -const isEnvTrue = (key: string) => - ["true", "1", "yes", "y"].includes((Deno.env.get(key) || "").toLowerCase()); +// TODO We should disable this adapter if midnight is disabled. +const midnight_enabled = !ENV.getBoolean("DISABLE_MIDNIGHT"); -const midnight_enabled = !isEnvTrue("DISABLE_MIDNIGHT"); const midnightNetworkUrls = { diff --git a/e2e/client/batcher/adapter-midnight.ts b/e2e/client/batcher/adapter-midnight.ts index b6c3088b0..9d3cfbec5 100644 --- a/e2e/client/batcher/adapter-midnight.ts +++ b/e2e/client/batcher/adapter-midnight.ts @@ -3,18 +3,15 @@ import { SimpleToken, witnesses } from "@e2e/midnight-contracts/eip-20"; import { MidnightAdapter } from "@effectstream/batcher"; import { midnightNetworkConfig } from "@effectstream/midnight-contracts/midnight-env"; import { buildWalletFacade } from "@effectstream/midnight-contracts/wallet-info"; - import { dirname, resolve } from "@std/path"; +import { ENV } from "@effectstream/utils/node-env"; // Resolve the base directory for midnight contracts const currentDir = dirname(new URL(import.meta.url).pathname); const midnightContractsDir = resolve(currentDir, "..", "..", "shared", "contracts", "midnight"); - -const isEnvTrue = (key: string) => - ["true", "1", "yes", "y"].includes((Deno.env.get(key) || "").toLowerCase()); - -const midnight_enabled = !isEnvTrue("DISABLE_MIDNIGHT"); +// TODO if midnight disabled we should skip the adapter construction, as it will fail. +const midnight_enabled = !ENV.getBoolean("DISABLE_MIDNIGHT"); const { contractInfo, contractAddress, zkConfigPath } = readMidnightContract( "contract-eip-20", diff --git a/e2e/client/batcher/config.ts b/e2e/client/batcher/config.ts index 54806c97b..d58bec809 100644 --- a/e2e/client/batcher/config.ts +++ b/e2e/client/batcher/config.ts @@ -3,10 +3,11 @@ import { type BatcherConfig, type DefaultBatcherInput, } from "@effectstream/batcher"; +import { ENV } from "@effectstream/utils/node-env"; // Config values mirroring e2e/client/node/scripts/start.ts const batchIntervalMs = 1000; -const port = Number(Deno.env.get("BATCHER_PORT") ?? "3334"); +const port = ENV.getNumber("BATCHER_PORT", 3334); // Batcher config matching old behavior export const config: BatcherConfig = { diff --git a/e2e/client/batcher/main.ts b/e2e/client/batcher/main.ts index 3156306ee..432817889 100644 --- a/e2e/client/batcher/main.ts +++ b/e2e/client/batcher/main.ts @@ -1,32 +1,7 @@ // Cleanup stale LevelDB BEFORE importing adapters (adapters initialize on import) -import * as path from "@std/path"; +import { ENV } from "@effectstream/utils/node-env"; -const isEnvTrue = (key: string) => ["true", "1", "yes", "y"].includes((Deno.env.get(key) || "").toLowerCase()); -const midnight_enabled = !isEnvTrue("DISABLE_MIDNIGHT"); - -// if (midnight_enabled) { -// const baseDir = path.dirname(path.fromFileUrl(import.meta.url)); - -// // Clean up all midnight LevelDB directories (including process-specific ones from previous runs) -// try { -// for await (const entry of Deno.readDir(baseDir)) { -// if (entry.isDirectory && entry.name.startsWith("midnight-level-db")) { -// const dbPath = path.join(baseDir, entry.name); -// try { -// await Deno.remove(dbPath, { recursive: true }); -// console.log(`๐Ÿงน Cleaned up stale Midnight LevelDB directory: ${entry.name}`); -// } catch (error) { -// console.warn(`โš ๏ธ Could not clean up ${entry.name}:`, error); -// } -// } -// } -// } catch (error) { -// // Ignore if baseDir doesn't exist or can't be read -// if (!(error instanceof Deno.errors.NotFound)) { -// console.warn("โš ๏ธ Error cleaning up LevelDB directories:", error); -// } -// } -// } +const midnight_enabled = !ENV.getBoolean("DISABLE_MIDNIGHT"); // Now import adapters after cleanup import { main, suspend } from "effection"; @@ -45,7 +20,7 @@ import { const batcher = createNewBatcher(config, storage); const batchIntervalMs = 1000; -const bitcoin_enabled = !isEnvTrue("DISABLE_BITCOIN"); +const bitcoin_enabled = !ENV.getBoolean("DISABLE_BITCOIN"); batcher .addBlockchainAdapter("paimal2", effectstreaml2Adapter, { criteriaType: "time", timeWindowMs: batchIntervalMs }) diff --git a/e2e/client/database/sql-to-ts.ts b/e2e/client/database/sql-to-ts.ts index d972dbe00..75b03a8f1 100644 --- a/e2e/client/database/sql-to-ts.ts +++ b/e2e/client/database/sql-to-ts.ts @@ -1,14 +1,15 @@ import { getConnection } from "@effectstream/db"; import { standAloneApplyMigrations } from "@effectstream/db-emulator"; import { migrationTable } from "./src/migration-order.ts"; -import { localhostConfig } from "@e2e/data-types"; +import { config } from "@e2e/data-types/config-localhost"; import { EvmCounterPrimitive } from "@e2e/node/custom-primitive"; +import { exit } from "@effectstream/utils/runtime"; // This helper applies Effectstream Migrations to the database, so you can use it to generate the pgtyped files. const db = await getConnection(); -await standAloneApplyMigrations(db, migrationTable, localhostConfig as any, { +await standAloneApplyMigrations(db as any, migrationTable, config as any, { "EVM:CUSTOM-COUNTER": EvmCounterPrimitive, }); console.log("โœ… System & User migrations applied"); -Deno.exit(0); +exit(0); diff --git a/e2e/client/node/deno.json b/e2e/client/node/deno.json index 9e333ed75..f4af36362 100644 --- a/e2e/client/node/deno.json +++ b/e2e/client/node/deno.json @@ -16,7 +16,7 @@ "node:start:e2e": "deno run -A --inspect --unstable-raw-imports src/main.ts", "node:start:testnet": "deno run -A --inspect --unstable-raw-imports src/main.testnet.ts", "clean_binaries": "cd ../../../packages/binaries/midnight-node && npm run clean && cd ../midnight-indexer && npm run clean && cd ../midnight-proof-server && npm run clean", - "quickstart": "deno task -f @effectstream/tui clean && EFFECTSTREAM_ENV=quickstart NODE_ENV=development deno run -A --check --unstable-raw-imports scripts/start.quickstart.ts", + "quickstart": "deno task -f @effectstream/tui clean && EFFECTSTREAM_ENV=quickstart NODE_ENV=development MIDNIGHT_STORAGE_PASSWORD=yourpasswordmypassword deno run -A --check --unstable-raw-imports scripts/start.quickstart.ts", "e2e": "EFFECTSTREAM_ENV=e2e deno run -A --unstable-raw-imports --check scripts/start.e2e.ts", "testnet": "EFFECTSTREAM_ENV=testnet deno run -A --unstable-raw-imports --check scripts/start.testnet.ts" }, diff --git a/e2e/client/node/e2e-tests/e2e.avail.test.ts b/e2e/client/node/e2e-tests/e2e.avail.test.ts index c27a736f8..adad13d2f 100644 --- a/e2e/client/node/e2e-tests/e2e.avail.test.ts +++ b/e2e/client/node/e2e-tests/e2e.avail.test.ts @@ -13,7 +13,8 @@ const AVAIL_NODE_URL = "ws://localhost:9955/ws"; const AVAIL_SEED: string = "//Alice"; const account = Account.new(AVAIL_SEED); -const isEnvTrue = (key: string) => ["true", "1", "yes", "y"].includes((Deno.env.get(key) || "").toLowerCase()); +import { getEnv } from "@effectstream/utils/runtime"; +const isEnvTrue = (key: string) => ["true", "1", "yes", "y"].includes((getEnv(key) || "").toLowerCase()); const avail_enabled = !isEnvTrue("DISABLE_AVAIL"); diff --git a/e2e/client/node/e2e-tests/e2e.midnight.test.ts b/e2e/client/node/e2e-tests/e2e.midnight.test.ts index 222175147..3f89f1d81 100644 --- a/e2e/client/node/e2e-tests/e2e.midnight.test.ts +++ b/e2e/client/node/e2e-tests/e2e.midnight.test.ts @@ -275,9 +275,11 @@ const configureProviders = ( /** * Get contract address from command line arguments or from a file */ +import { args, exit } from "@effectstream/utils/runtime"; + const getContractAddress = (): string => { // First try to get from command line arguments - const contractAddressFromArgs = Deno.args[0]; + const contractAddressFromArgs = args()[0]; if (contractAddressFromArgs) { console.log( @@ -314,7 +316,7 @@ const getContractAddress = (): string => { console.error( "Example: deno run --allow-all increment.ts 0x1234567890abcdef1234567890abcdef12345678", ); - Deno.exit(1); + exit(1); } }; @@ -446,7 +448,6 @@ async function joinAndIncrementTest( } catch (error) { console.error("โŒ Error during join and increment process:", error); console.error("โŒ Error:", error instanceof Error ? error.message : error); - // Deno.exit(1); } finally { // Clean up wallet if (walletResult) { diff --git a/e2e/client/node/scripts/midnight-eip20-submit.ts b/e2e/client/node/scripts/midnight-eip20-submit.ts index c3983fc35..2d4ced5bb 100644 --- a/e2e/client/node/scripts/midnight-eip20-submit.ts +++ b/e2e/client/node/scripts/midnight-eip20-submit.ts @@ -1,4 +1,5 @@ import { setNetworkId } from "@midnight-ntwrk/midnight-js-network-id"; +import { args, getEnv, exit } from "@effectstream/utils/runtime"; import { findDeployedContract } from "@midnight-ntwrk/midnight-js-contracts"; import { httpClientProofProvider, @@ -81,10 +82,10 @@ function createWalletProvider( } async function main() { - const payloadArg = Deno.args[0]; + const payloadArg = args()[0]; const payload = payloadArg ? JSON.parse(payloadArg) : DEFAULT_PAYLOAD; - const seed = Deno.env.get("MIDNIGHT_WALLET_SEED") ?? GENESIS_SEED; + const seed = getEnv("MIDNIGHT_WALLET_SEED") ?? GENESIS_SEED; const { contractAddress, contractInfo, zkConfigPath } = readMidnightContract( "contract-eip-20", @@ -154,6 +155,6 @@ async function main() { if (import.meta.main) { main().catch((err) => { console.error("Mint submission failed:", err); - Deno.exit(1); + exit(1); }); } diff --git a/e2e/client/node/scripts/start.e2e.ts b/e2e/client/node/scripts/start.e2e.ts index 91f66160c..264780af5 100644 --- a/e2e/client/node/scripts/start.e2e.ts +++ b/e2e/client/node/scripts/start.e2e.ts @@ -22,6 +22,7 @@ import { RPCTest } from "../e2e-tests/e2e.rpc.test.ts"; import { tokenTests } from "../e2e-tests/e2e.tokens.ts"; import { bitcoinTest, bitcoinBatcherTest } from "../e2e-tests/e2e.bitcoin.test.ts"; import { getEffectstreamEVMPublicClient } from "@e2e/engine"; +import { getEnv, exit } from "@effectstream/utils/runtime"; import type { Client, PoolConfig } from "pg"; import pg from "pg"; @@ -328,7 +329,7 @@ export async function getDBConnection(): Promise { // Optional pause to allow the user to inspect the DB, // check the logs, send more requests, etc. - const pauseTime = Deno.env.get("EFFECTSTREAM_E2E_PAUSE_TIME"); + const pauseTime = getEnv("EFFECTSTREAM_E2E_PAUSE_TIME"); if (pauseTime) { console.log("โณ Pausing for", pauseTime, "seconds"); await delay(parseInt(pauseTime, 10) * 1000); @@ -345,7 +346,7 @@ export async function getDBConnection(): Promise { shutdown(); } finally { if (anyError()) { - Deno.exit(1); + exit(1); } } } @@ -353,10 +354,10 @@ export async function getDBConnection(): Promise { test() .then(() => { console.log("๐ŸŽ‰ Test completed"); - Deno.exit(0); + exit(0); }).catch((e) => { console.log("โŒ Test failed"); console.error(e); - Deno.exit(1); + exit(1); }); \ No newline at end of file diff --git a/e2e/client/node/scripts/start.testnet.ts b/e2e/client/node/scripts/start.testnet.ts index 1a14ee676..83758856f 100644 --- a/e2e/client/node/scripts/start.testnet.ts +++ b/e2e/client/node/scripts/start.testnet.ts @@ -14,15 +14,15 @@ import { const disableStderr = logs !== "stdout"; const external_db_enabled = ENV.getBoolean("EXTERNAL_DB_ENABLED"); - const midnightWalletSeed = Deno.env.get("MIDNIGHT_WALLET_SEED"); + const midnightWalletSeed = ENV.getString("MIDNIGHT_WALLET_SEED"); if (!midnightWalletSeed) { throw new Error("MIDNIGHT_WALLET_SEED is not set"); } const shouldLaunchProofServer = !isExternalProofServerConfigured; const shouldInjectProofServerEnv = - !Deno.env.get("MIDNIGHT_PROOF_SERVER_URL") && - !Deno.env.get("MIDNIGHT_PROOF_SERVER"); + !ENV.getString("MIDNIGHT_PROOF_SERVER_URL") && + !ENV.getString("MIDNIGHT_PROOF_SERVER"); const proofServerEnv = shouldInjectProofServerEnv ? { MIDNIGHT_PROOF_SERVER_URL: midnightNetworkConfig.proofServer } : undefined; diff --git a/e2e/e2e-engine/e2e-assert.ts b/e2e/e2e-engine/e2e-assert.ts index f62e2b5c6..d91ed1bf8 100644 --- a/e2e/e2e-engine/e2e-assert.ts +++ b/e2e/e2e-engine/e2e-assert.ts @@ -1,14 +1,10 @@ import type { Pool } from "pg"; import { type QueryResult, safeQuery } from "./e2e-db.ts"; +import { ENV } from "@effectstream/utils/node-env"; const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); -const getMaxTimeout = (): number => { - if (Deno.env.get("E2E_MAX_TIMEOUT")) { - return parseInt(Deno.env.get("E2E_MAX_TIMEOUT")!, 10); - } - return 20000; -}; +const getMaxTimeout = (): number => ENV.getNumber("E2E_MAX_TIMEOUT", 20000); const testResults = { count: 0, diff --git a/e2e/e2e-wallets/server/main.ts b/e2e/e2e-wallets/server/main.ts index 67e0aea92..310343c83 100644 --- a/e2e/e2e-wallets/server/main.ts +++ b/e2e/e2e-wallets/server/main.ts @@ -1,14 +1,15 @@ import { Application } from "@oak/oak/application"; import { Router } from "@oak/oak/router"; import routeStaticFilesFrom from "./util/routeStaticFilesFrom.ts"; +import { cwd } from "@effectstream/utils/runtime"; export const app = new Application(); const router = new Router(); app.use(router.routes()); app.use(routeStaticFilesFrom([ - `${Deno.cwd()}/client/dist`, - `${Deno.cwd()}/client/public`, + `${cwd()}/client/dist`, + `${cwd()}/client/public`, ])); // Default Wallets E2E diff --git a/e2e/e2e-wallets/vite.config.ts b/e2e/e2e-wallets/vite.config.ts index 2596603b4..3be76fa35 100644 --- a/e2e/e2e-wallets/vite.config.ts +++ b/e2e/e2e-wallets/vite.config.ts @@ -28,12 +28,15 @@ const dbEmptyPath = join(dirname(fromFileUrl(import.meta.url)), "effectstream-db export default defineConfig({ define: { "process.env.EFFECTSTREAM_ENV": JSON.stringify("local"), + Deno: undefined, + Bun: undefined, }, sourcemap: true, resolve: { alias: { "@e2e/midnight-contract-eip-20/contract": midnightContractEip20Path + "index.js", "@e2e/midnight-contract-counter-basic/contract": midnightContractCounterBasicPath + "index.js", + "@effectstream/utils/runtime": utilsPath + "src/runtime.ts", "@effectstream/utils": utilsPath + "src/mod.ts", "@effectstream/config": configPath + "src/mod.ts", "@effectstream/concise": concisePath + "src/mod.ts", diff --git a/e2e/shared/contracts/avail/deploy.ts b/e2e/shared/contracts/avail/deploy.ts index 401a72ea0..5f723095e 100644 --- a/e2e/shared/contracts/avail/deploy.ts +++ b/e2e/shared/contracts/avail/deploy.ts @@ -1,7 +1,9 @@ import { Account, Pallets, SDK } from "avail-js-sdk"; +import { getEnv, cwd, spawn } from "@effectstream/utils/runtime"; +import { writeFile } from "node:fs/promises"; const sdk = await SDK.New("ws://localhost:9955/ws"); -const seed: string = Deno.env.get("SEED") ?? "//Alice"; +const seed: string = getEnv("SEED") ?? "//Alice"; if (!seed) { throw new Error("SEED environment variable is not set"); } @@ -50,16 +52,18 @@ export async function createApplicationKey() { const { appId, txHash } = await createApplicationKey(); console.log("Transaction Hash: ", txHash.toString()); const data = JSON.stringify({ appId, txHash, ApplicationKey, genesisHash }); -const fileName = Deno.cwd() + "/avail_app.json"; +const fileName = cwd() + "/avail_app.json"; console.log("Writing to file: ", fileName); -await Deno.writeTextFile(fileName, data); +await writeFile(fileName, data, "utf-8"); -const child = new Deno.Command("deno", { +const child = spawn("deno", { args: ["task", "-f", "@e2e/avail-contracts", "avail-light-client:start"], env: { AVAIL_APP_ID: appId.toString(), }, -}).spawn(); + stdout: "inherit", + stderr: "inherit", +}); console.log("Light Client Started"); diff --git a/e2e/shared/contracts/avail/read-app.ts b/e2e/shared/contracts/avail/read-app.ts index 8e42c2419..c3c04ea91 100644 --- a/e2e/shared/contracts/avail/read-app.ts +++ b/e2e/shared/contracts/avail/read-app.ts @@ -1,3 +1,4 @@ +import fs from "node:fs"; export type AvailApplicationInfo = { appId: number; txHash: { // The txHash of the apps creation transaction @@ -15,7 +16,7 @@ export function readAvailApplication(): AvailApplicationInfo { const dir = new URL(".", import.meta.url); // Construct the full path to avail_app.json const appInfoPath = new URL("avail_app.json", dir); - const appInfoJson = Deno.readTextFileSync(appInfoPath); + const appInfoJson = fs.readFileSync(appInfoPath, "utf-8"); const appInfo = JSON.parse(appInfoJson) as AvailApplicationInfo; cachedAppInfo = appInfo; return appInfo; diff --git a/e2e/shared/contracts/bitcoin/generate-blocks.ts b/e2e/shared/contracts/bitcoin/generate-blocks.ts index 220191a53..3a057dd92 100644 --- a/e2e/shared/contracts/bitcoin/generate-blocks.ts +++ b/e2e/shared/contracts/bitcoin/generate-blocks.ts @@ -2,6 +2,7 @@ import * as bitcoin from 'bitcoinjs-lib'; import * as ecpair from 'ecpair'; import * as ecc from 'tiny-secp256k1'; import { createHash } from "node:crypto"; +import { args, exit } from "@effectstream/utils/runtime"; /** * @@ -20,7 +21,10 @@ const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); const ECPair = ecpair.ECPairFactory(ecc); const SATS_PER_BTC = 100_000_000; -const DEFAULT_BLOCK_INTERVAL = Deno.args.includes('--block-interval') ? parseInt(Deno.args[Deno.args.indexOf('--block-interval') + 1]) : 5000; +const argv = args(); +const DEFAULT_BLOCK_INTERVAL = argv.includes('--block-interval') + ? parseInt(argv[argv.indexOf('--block-interval') + 1]) + : 5000; const NETWORK = bitcoin.networks.regtest; console.log(`Using block interval: ${DEFAULT_BLOCK_INTERVAL}ms`); @@ -88,20 +92,18 @@ const bitcoinRpcCall = async (method: string, params: any[] = [], walletName?: s let running = true; -// Handle process signals -if (typeof Deno !== 'undefined') { - Deno.addSignalListener('SIGINT', () => { - console.log('\nReceived SIGINT, stopping block generation...'); - running = false; - Deno.exit(130); - }); +// Handle process signals (Node-style; Deno 2 has node compat for process) +process.on("SIGINT", () => { + console.log('\nReceived SIGINT, stopping block generation...'); + running = false; + exit(130); +}); - Deno.addSignalListener('SIGTERM', () => { - console.log('\nReceived SIGTERM, stopping block generation...'); - running = false; - Deno.exit(143); - }); -} +process.on("SIGTERM", () => { + console.log('\nReceived SIGTERM, stopping block generation...'); + running = false; + exit(143); +}); async function generateTXHex(address: string, amountSats: number, inputUTXO: { txid: string, vout: number }) { const keyPair = ECPair.fromWIF(target.privateKey, NETWORK); @@ -179,8 +181,8 @@ async function main() { } console.log(`Using mining wallet address: ${address}`); } catch (error) { - console.error('Failed to get address. Make sure Bitcoin Core is running and accessible.'); - Deno.exit(1); + console.error('Failed to get address. Make sure Bitcoin Core is running and accessible.'); + exit(1); } // Import batcher address so we can track its funds @@ -337,6 +339,6 @@ async function main() { main().catch((error) => { console.error('Fatal error:', error); - Deno.exit(1); + exit(1); }); diff --git a/e2e/shared/contracts/cardano/fill-template.ts b/e2e/shared/contracts/cardano/fill-template.ts index 2369f2c44..7333e084e 100644 --- a/e2e/shared/contracts/cardano/fill-template.ts +++ b/e2e/shared/contracts/cardano/fill-template.ts @@ -25,7 +25,7 @@ async function fetchAndSaveGenesis( const filePath = `${TEMP_DIR}/${type}-genesis.json`; await fs.mkdir(TEMP_DIR, { recursive: true }); - await Deno.writeTextFile(filePath, JSON.stringify(json, null, 2)); + await fs.writeFile(filePath, JSON.stringify(json, null, 2), "utf-8"); return filePath; } @@ -39,7 +39,7 @@ async function updateDolosConfig() { ); // Read the template file - const templateContent = await Deno.readTextFile(TEMPLATE_FILE); + const templateContent = await fs.readFile(TEMPLATE_FILE, "utf-8"); const config = parseToml(templateContent); // Update genesis paths @@ -51,7 +51,7 @@ async function updateDolosConfig() { }; // Write updated config back to file - await Deno.writeTextFile(FINAL_TOML, stringifyToml(config)); + await fs.writeFile(FINAL_TOML, stringifyToml(config), "utf-8"); } // Execute the update diff --git a/e2e/shared/contracts/midnight/contract-counter-deploy.ts b/e2e/shared/contracts/midnight/contract-counter-deploy.ts index 58c893902..d732e7793 100644 --- a/e2e/shared/contracts/midnight/contract-counter-deploy.ts +++ b/e2e/shared/contracts/midnight/contract-counter-deploy.ts @@ -5,6 +5,7 @@ import { type CounterPrivateState, witnesses, } from "./contract-counter/src/index.ts"; +import { exit } from "@effectstream/utils/runtime"; const config: DeployConfig = { contractName: "contract-counter", @@ -22,9 +23,9 @@ console.log("Deploying contract with network config:", midnightNetworkConfig); deployMidnightContract(config, midnightNetworkConfig) .then(() => { console.log("Deployment successful"); - Deno.exit(0); + exit(0); }) .catch((e) => { console.error("Unhandled error:", e); - Deno.exit(1); + exit(1); }); diff --git a/e2e/shared/contracts/midnight/contract-counter-interact.ts b/e2e/shared/contracts/midnight/contract-counter-interact.ts index 520a37405..0067ebdd2 100644 --- a/e2e/shared/contracts/midnight/contract-counter-interact.ts +++ b/e2e/shared/contracts/midnight/contract-counter-interact.ts @@ -49,6 +49,8 @@ import { import { dirname, resolve } from "@std/path"; import { exists } from "@std/fs"; import { CompiledContract } from '@midnight-ntwrk/compact-js'; +import { getEnv, args, exit } from "@effectstream/utils/runtime"; +import { readFile, readTextFile } from "node:fs/promises"; // @ts-expect-error: It's needed to enable WebSocket usage through apollo globalThis.WebSocket = WebSocket; @@ -253,7 +255,7 @@ const buildWalletAndWaitForFunds = async ( seed: string, filename: string, ): Promise => { - const directoryPath = Deno.env.get("SYNC_CACHE"); + const directoryPath = getEnv("SYNC_CACHE"); let wallet: Wallet & Resource; if (directoryPath !== undefined) { const fullPath = `${directoryPath}/${filename}`; @@ -262,7 +264,7 @@ const buildWalletAndWaitForFunds = async ( `Attempting to restore state from ${fullPath}`, ); try { - const serialized = await Deno.readFile(fullPath); + const serialized = await readFile(fullPath); wallet = await WalletBuilder.restore( indexer, indexerWS, @@ -365,7 +367,7 @@ const configureProviders = async ( */ const getContractAddress = async (): Promise => { // First try to get from command line arguments - const contractAddressFromArgs = Deno.args[0]; + const contractAddressFromArgs = args()[0]; if (contractAddressFromArgs) { console.log( @@ -380,7 +382,7 @@ const getContractAddress = async (): Promise => { try { if (await exists(contractAddressFile)) { const contractAddressFromFile = JSON.parse( - await Deno.readTextFile(contractAddressFile), + await readTextFile(contractAddressFile, "utf-8"), ).contractAddress; if (contractAddressFromFile) { @@ -408,7 +410,7 @@ const getContractAddress = async (): Promise => { console.error( "Example: deno run --allow-all increment.ts 0x1234567890abcdef1234567890abcdef12345678", ); - Deno.exit(1); + exit(1); } }; @@ -484,13 +486,13 @@ async function joinAndIncrement(): Promise { } catch (error) { console.error("โŒ Error during join and increment process:", error); console.error("โŒ Error:", error instanceof Error ? error.message : error); - Deno.exit(1); + exit(1); } finally { // Clean up wallet if (wallet) { try { console.log("๐Ÿงน Wallet closed successfully"); - Deno.exit(0); + exit(0); } catch (error) { console.error("โŒ Error closing wallet:", error); } @@ -502,7 +504,7 @@ async function joinAndIncrement(): Promise { if (import.meta.main) { joinAndIncrement().catch((error) => { console.error("โŒ Unhandled error:", error); - Deno.exit(1); + exit(1); }); } diff --git a/e2e/shared/contracts/midnight/contract-counter/convert-js.ts b/e2e/shared/contracts/midnight/contract-counter/convert-js.ts deleted file mode 100644 index 6382510a5..000000000 --- a/e2e/shared/contracts/midnight/contract-counter/convert-js.ts +++ /dev/null @@ -1,33 +0,0 @@ -// Convert js files to ts files. -// -// This is a temporal solution for converting the .cjs file to .ts -// This is a partial conversion and might fail for some cases. -// The goal is doing the minimal conversion to make it a valid ts file. -// We expect the compiler to generate the .mjs or .ts eventually. -export async function convertJS(jsPath: string, tsPath: string) { - const jsFile = await Deno.readTextFile(jsPath); - const output = jsFile - // 1. Remove 'use strict'. - .replace(/^["']use strict["'];?/, "") - // 2. Replace const r = require('lib') with import * as r from 'lib'. - .replace( - /(const|let|var) (\w+) = require\(['"](.*)["']\);?/g, - "import * as $2 from '$3';", - ) - // replace exports.foo = foo; with export { foo }; - .replace(/exports\.(\w+) = (\w+);?/g, "export { $1 };"); - - await Deno.writeTextFile(tsPath, output); - console.log(`Converted ${jsPath} to ${tsPath}`); -} - -if (import.meta.main) { - const args = Deno.args; - if (args.length !== 2) { - console.error( - "Usage: deno run -A convert-js.ts ", - ); - Deno.exit(1); - } - await convertJS(args[0], args[1]); -} diff --git a/e2e/shared/contracts/midnight/contract-eip-20-deploy.ts b/e2e/shared/contracts/midnight/contract-eip-20-deploy.ts index 2bdfb429f..2b8d2c1e3 100644 --- a/e2e/shared/contracts/midnight/contract-eip-20-deploy.ts +++ b/e2e/shared/contracts/midnight/contract-eip-20-deploy.ts @@ -4,6 +4,7 @@ import { SimpleToken, witnesses, } from "./contract-eip-20/src/index.original.ts"; +import { exit } from "@effectstream/utils/runtime"; const config: DeployConfig = { contractName: "contract-eip-20", @@ -30,9 +31,9 @@ console.log("Deploying contract with network config:", midnightNetworkConfig); deployMidnightContract(config, midnightNetworkConfig) .then(() => { console.log("Deployment successful"); - Deno.exit(0); + exit(0); }) .catch((e) => { console.error("Unhandled error:", e); - Deno.exit(1); + exit(1); }); diff --git a/e2e/shared/contracts/midnight/contract-eip-20-interact.ts b/e2e/shared/contracts/midnight/contract-eip-20-interact.ts index 8d68610ed..90a271c78 100644 --- a/e2e/shared/contracts/midnight/contract-eip-20-interact.ts +++ b/e2e/shared/contracts/midnight/contract-eip-20-interact.ts @@ -47,6 +47,8 @@ import { } from "npm:@midnight-ntwrk/midnight-js-network-id"; import { dirname, resolve } from "@std/path"; import { exists } from "@std/fs"; +import { getEnv, args, exit } from "@effectstream/utils/runtime"; +import { readFile, readTextFile } from "node:fs/promises"; globalThis.WebSocket = WebSocket; @@ -262,7 +264,7 @@ const buildWalletAndWaitForFunds = async ( seed: string, filename: string, ): Promise => { - const directoryPath = Deno.env.get("SYNC_CACHE"); + const directoryPath = getEnv("SYNC_CACHE"); let wallet: Wallet & Resource; if (directoryPath !== undefined) { const fullPath = `${directoryPath}/${filename}`; @@ -271,7 +273,7 @@ const buildWalletAndWaitForFunds = async ( `Attempting to restore state from ${fullPath}`, ); try { - const serialized = await Deno.readFile(fullPath); + const serialized = await readFile(fullPath); wallet = await WalletBuilder.restore( indexer, indexerWS, @@ -369,7 +371,7 @@ const configureProviders = async ( const getContractAddress = async (): Promise => { // First try to get from command line arguments - // const contractAddressFromArgs = Deno.args[0]; + // const contractAddressFromArgs = args()[0]; // if (contractAddressFromArgs) { // console.log( @@ -384,7 +386,7 @@ const getContractAddress = async (): Promise => { try { if (await exists(contractAddressFile)) { const contractAddressFromFile = JSON.parse( - await Deno.readTextFile(contractAddressFile), + await readTextFile(contractAddressFile, "utf-8"), ).contractAddress; if (contractAddressFromFile) { @@ -412,7 +414,7 @@ const getContractAddress = async (): Promise => { console.error( "Example: deno run --allow-all increment.ts 0x1234567890abcdef1234567890abcdef12345678", ); - Deno.exit(1); + exit(1); } }; @@ -477,13 +479,13 @@ async function joinAndMint(account: string, amount: bigint): Promise { } catch (error) { console.error("โŒ Error during join and mint process:", error); console.error("โŒ Error:", error instanceof Error ? error.message : error); - Deno.exit(1); + exit(1); } finally { // Clean up wallet if (wallet) { try { console.log("๐Ÿงน Wallet closed successfully"); - Deno.exit(0); + exit(0); } catch (error) { console.error("โŒ Error closing wallet:", error); } @@ -504,7 +506,7 @@ if (import.meta.main) { const address = (await Rx.firstValueFrom(wallet.state())).address; joinAndMint(address, 20000n).catch((error) => { console.error("โŒ Unhandled error:", error); - Deno.exit(1); + exit(1); }); } diff --git a/e2e/shared/contracts/midnight/contract-eip-20/convert-js.ts b/e2e/shared/contracts/midnight/contract-eip-20/convert-js.ts deleted file mode 100644 index 6382510a5..000000000 --- a/e2e/shared/contracts/midnight/contract-eip-20/convert-js.ts +++ /dev/null @@ -1,33 +0,0 @@ -// Convert js files to ts files. -// -// This is a temporal solution for converting the .cjs file to .ts -// This is a partial conversion and might fail for some cases. -// The goal is doing the minimal conversion to make it a valid ts file. -// We expect the compiler to generate the .mjs or .ts eventually. -export async function convertJS(jsPath: string, tsPath: string) { - const jsFile = await Deno.readTextFile(jsPath); - const output = jsFile - // 1. Remove 'use strict'. - .replace(/^["']use strict["'];?/, "") - // 2. Replace const r = require('lib') with import * as r from 'lib'. - .replace( - /(const|let|var) (\w+) = require\(['"](.*)["']\);?/g, - "import * as $2 from '$3';", - ) - // replace exports.foo = foo; with export { foo }; - .replace(/exports\.(\w+) = (\w+);?/g, "export { $1 };"); - - await Deno.writeTextFile(tsPath, output); - console.log(`Converted ${jsPath} to ${tsPath}`); -} - -if (import.meta.main) { - const args = Deno.args; - if (args.length !== 2) { - console.error( - "Usage: deno run -A convert-js.ts ", - ); - Deno.exit(1); - } - await convertJS(args[0], args[1]); -} diff --git a/e2e/shared/contracts/midnight/faucet.ts b/e2e/shared/contracts/midnight/faucet.ts index c29876522..467681f9f 100644 --- a/e2e/shared/contracts/midnight/faucet.ts +++ b/e2e/shared/contracts/midnight/faucet.ts @@ -22,6 +22,7 @@ import { } from "@midnight-ntwrk/ledger-v7"; import { NetworkId } from "@midnight-ntwrk/wallet-sdk-abstractions"; import type { DefaultV1Configuration } from "@midnight-ntwrk/wallet-sdk-shielded/v1"; +import { getEnv, exit } from "@effectstream/utils/runtime"; /** * This script transfers 10.0 dust from the default midnight wallet to a given address. @@ -254,7 +255,7 @@ export function getInitialShieldedState( * Resolve sync timeout from env or default. */ export function resolveWalletSyncTimeoutMs(): number { - const envValue = Deno.env.get("MIDNIGHT_WALLET_SYNC_TIMEOUT_MS"); + const envValue = getEnv("MIDNIGHT_WALLET_SYNC_TIMEOUT_MS"); if (!envValue) return WALLET_SYNC_TIMEOUT_MS; const parsed = Number(envValue); if (Number.isFinite(parsed) && parsed > 0) return parsed; @@ -265,7 +266,7 @@ export function resolveWalletSyncTimeoutMs(): number { } const resolveDustFeeBlocksMargin = (): number => { - const envValue = Deno.env.get("MIDNIGHT_DUST_FEE_BLOCKS_MARGIN"); + const envValue = getEnv("MIDNIGHT_DUST_FEE_BLOCKS_MARGIN"); if (!envValue) return DUST_FEE_BLOCKS_MARGIN; const parsed = Number(envValue); if (Number.isFinite(parsed) && parsed >= 0) return Math.floor(parsed); @@ -276,7 +277,7 @@ const resolveDustFeeBlocksMargin = (): number => { }; const resolveDustFeeOverhead = (): bigint => { - const envValue = Deno.env.get("MIDNIGHT_DUST_FEE_OVERHEAD"); + const envValue = getEnv("MIDNIGHT_DUST_FEE_OVERHEAD"); if (!envValue) return DUST_FEE_OVERHEAD; try { return BigInt(envValue); @@ -662,16 +663,16 @@ export async function registerNightForDust( } const resolveNetworkUrls = (): Required => ({ - indexer: Deno.env.get("MIDNIGHT_INDEXER_URL") || DEFAULT_NETWORK_URLS.indexer, - indexerWS: Deno.env.get("MIDNIGHT_INDEXER_WS_URL") || + indexer: getEnv("MIDNIGHT_INDEXER_URL") || DEFAULT_NETWORK_URLS.indexer, + indexerWS: getEnv("MIDNIGHT_INDEXER_WS_URL") || DEFAULT_NETWORK_URLS.indexerWS, - node: Deno.env.get("MIDNIGHT_NODE_URL") || DEFAULT_NETWORK_URLS.node, - proofServer: Deno.env.get("MIDNIGHT_PROOF_SERVER_URL") || + node: getEnv("MIDNIGHT_NODE_URL") || DEFAULT_NETWORK_URLS.node, + proofServer: getEnv("MIDNIGHT_PROOF_SERVER_URL") || DEFAULT_NETWORK_URLS.proofServer, }); const resolveNetworkId = (): NetworkId.NetworkId => { - const networkIdRaw = Deno.env.get("MIDNIGHT_NETWORK_ID") || "undeployed"; + const networkIdRaw = getEnv("MIDNIGHT_NETWORK_ID") || "undeployed"; switch (networkIdRaw.toLowerCase()) { case "undeployed": return NetworkId.NetworkId.Undeployed; @@ -876,19 +877,19 @@ export const faucet = async ( }; if (import.meta.main) { - const midnightAddress = Deno.env.get("MIDNIGHT_ADDRESS"); + const midnightAddress = getEnv("MIDNIGHT_ADDRESS"); if (!midnightAddress) { console.error("โŒ MIDNIGHT_ADDRESS environment variable is not set"); console.error( "Example: MIDNIGHT_ADDRESS=mn_addr_undeployed1k7dst6qphntqmypwa4mhyltk794wx4lt07kherlc9y6clu5swssxqr9xe4z7txy8rscldhec7nmm47ujccf7syky0wz86jwahhkfd3mvq9wu8qx deno run -A faucet.ts", ); - Deno.exit(1); + exit(1); } try { await faucet(midnightAddress); - Deno.exit(0); + exit(0); } catch (error) { console.error("โŒ Error during faucet process:", error); - Deno.exit(1); + exit(1); } } diff --git a/e2e/shared/data-types/src/config.testnet.ts b/e2e/shared/data-types/src/config.testnet.ts index ecc49ce6f..cc15b1dc5 100644 --- a/e2e/shared/data-types/src/config.testnet.ts +++ b/e2e/shared/data-types/src/config.testnet.ts @@ -14,6 +14,7 @@ import { PrimitiveTypeMidnightGeneric, } from "@effectstream/sm/builtin"; import { arbitrumSepolia } from "viem/chains"; +import { getEnv } from "@effectstream/utils/runtime"; import { paimaL2Grammar } from "./grammar.ts"; @@ -38,9 +39,7 @@ let midnightTip: number = 1111111; // IMPORTANT: For testing purposes. Setting it to true, will // use a new tip on each restart, making the db inconsistent. const USE_TESTING_TIP = true; -const arbitrumSepoliaRpc = Deno - ? Deno.env.get("ARBITRUM_SEPOLIA_RPC") - : undefined; +const arbitrumSepoliaRpc = getEnv("ARBITRUM_SEPOLIA_RPC"); type ContractAddressBook = Record>; const contractAddressBook = contractAddressesEvmMain() as ContractAddressBook; diff --git a/e2e/shared/data-types/src/config.ts b/e2e/shared/data-types/src/config.ts index 627ace515..377b15514 100644 --- a/e2e/shared/data-types/src/config.ts +++ b/e2e/shared/data-types/src/config.ts @@ -26,10 +26,17 @@ import { } from "@effectstream/sm/builtin"; import * as SimpleTokenContract from "@e2e/midnight-contract-eip-20/contract"; import * as CounterContract from "@e2e/midnight-contract-counter-basic/contract"; +import { getEnv } from "@effectstream/utils/runtime"; + +const isBackendEnvironment = typeof process !== "undefined" && process.env; + +const isEnvTrue = (key: string) => { + const val = isBackendEnvironment + ? getEnv(key) + : (import.meta as any).env["VITE_" + key]; + return ["true", "1", "yes", "y"].includes((val || "").toLowerCase()); +}; -const isEnvTrue = (typeof Deno !== 'undefined' && Deno) ? - ((key: string) => ["true", "1", "yes", "y"].includes((Deno.env.get(key) || "").toLowerCase())) - : ((key: string) => ["true", "1", "yes", "y"].includes(((import.meta as any).env['VITE_' + key] || "").toLowerCase())); // TODO: This is a workaround to disable yaci-devkit in linux for testing. // There is a unknown error when launching this process. diff --git a/packages/batcher/adapters/evm-contract-adapter.test.ts b/packages/batcher/adapters/evm-contract-adapter.test.ts index 637a09dca..9ada9e22a 100644 --- a/packages/batcher/adapters/evm-contract-adapter.test.ts +++ b/packages/batcher/adapters/evm-contract-adapter.test.ts @@ -1,4 +1,5 @@ import { AddressType } from "@effectstream/utils"; +import { test } from "@effectstream/utils/runtime"; import { assertEquals } from "jsr:@std/assert"; import { EvmContractAdapter } from "./evm-contract-adapter.ts"; import type { HardhatArtifact } from "./evm-contract-adapter.ts"; @@ -49,14 +50,14 @@ function makeInput(payload: unknown): DefaultBatcherInput { }; } -Deno.test("EvmContractAdapter.validateInput accepts known method", async () => { +test("EvmContractAdapter.validateInput accepts known method", async () => { const adapter = new EvmContractAdapter(TEST_CONFIG); const input = makeInput({ method: "incrementCounter", args: [] }); const result = await adapter.validateInput(input); assertEquals(result.valid, true); }); -Deno.test("EvmContractAdapter.validateInput rejects unknown method", async () => { +test("EvmContractAdapter.validateInput rejects unknown method", async () => { const adapter = new EvmContractAdapter(TEST_CONFIG); const input = makeInput({ method: "doesNotExist", args: [] }); const result = await adapter.validateInput(input); @@ -67,7 +68,7 @@ Deno.test("EvmContractAdapter.validateInput rejects unknown method", async () => ); }); -Deno.test("EvmContractAdapter.validateInput enforces nonpayable value", async () => { +test("EvmContractAdapter.validateInput enforces nonpayable value", async () => { const adapter = new EvmContractAdapter(TEST_CONFIG); const input = makeInput({ method: "incrementCounter", diff --git a/packages/batcher/batch-data-builder/default-builder-logic.test.ts b/packages/batcher/batch-data-builder/default-builder-logic.test.ts index a89b51274..71d5ec011 100644 --- a/packages/batcher/batch-data-builder/default-builder-logic.test.ts +++ b/packages/batcher/batch-data-builder/default-builder-logic.test.ts @@ -1,4 +1,5 @@ import { assertEquals, assertThrows } from "jsr:@std/assert"; +import { test } from "@effectstream/utils/runtime"; import { DefaultBatchBuilderLogic } from "./default-builder-logic.ts"; import { AddressType } from "@effectstream/utils"; @@ -10,13 +11,13 @@ const MOCK_INPUT = { timestamp: "1234567890", }; -Deno.test("DefaultBatchBuilderLogic - returns null for empty inputs", () => { +test("DefaultBatchBuilderLogic - returns null for empty inputs", () => { const builder = new DefaultBatchBuilderLogic(); const result = builder.buildBatchData([], { maxSize: 1000 }); assertEquals(result, null); }); -Deno.test("DefaultBatchBuilderLogic - builds batch with single input", () => { +test("DefaultBatchBuilderLogic - builds batch with single input", () => { const builder = new DefaultBatchBuilderLogic(); const result = builder.buildBatchData([MOCK_INPUT], { maxSize: 1000 }); @@ -35,7 +36,7 @@ Deno.test("DefaultBatchBuilderLogic - builds batch with single input", () => { assertEquals(inner[4], MOCK_INPUT.timestamp); }); -Deno.test("DefaultBatchBuilderLogic - respects maxSize", () => { +test("DefaultBatchBuilderLogic - respects maxSize", () => { const builder = new DefaultBatchBuilderLogic(); // Provide a very small max size that shouldn't fit even one input const result = builder.buildBatchData([MOCK_INPUT], { maxSize: 10 }); @@ -45,7 +46,7 @@ Deno.test("DefaultBatchBuilderLogic - respects maxSize", () => { assertEquals(result, null); }); -Deno.test("DefaultBatchBuilderLogic - batches multiple inputs if they fit", () => { +test("DefaultBatchBuilderLogic - batches multiple inputs if they fit", () => { const builder = new DefaultBatchBuilderLogic(); const result = builder.buildBatchData([MOCK_INPUT, MOCK_INPUT], { maxSize: 1000 }); diff --git a/packages/batcher/batch-data-builder/evm-builder-logic.test.ts b/packages/batcher/batch-data-builder/evm-builder-logic.test.ts index 3d0dc7bb8..03d7e69ae 100644 --- a/packages/batcher/batch-data-builder/evm-builder-logic.test.ts +++ b/packages/batcher/batch-data-builder/evm-builder-logic.test.ts @@ -1,4 +1,5 @@ import { AddressType } from "@effectstream/utils"; +import { test } from "@effectstream/utils/runtime"; import { assertEquals } from "jsr:@std/assert"; import { EvmBatchBuilderLogic } from "./evm-builder-logic.ts"; @@ -16,13 +17,13 @@ function makeInput(overrides: Partial> = {}) { }; } -Deno.test("EvmBatchBuilderLogic - returns null for empty inputs", () => { +test("EvmBatchBuilderLogic - returns null for empty inputs", () => { const builder = new EvmBatchBuilderLogic(); const result = builder.buildBatchData([], { maxSize: 1000 }); assertEquals(result, null); }); -Deno.test("EvmBatchBuilderLogic - batches valid input", () => { +test("EvmBatchBuilderLogic - batches valid input", () => { const builder = new EvmBatchBuilderLogic(); const input = makeInput(); const result = builder.buildBatchData([input], { maxSize: 1000 }); @@ -35,7 +36,7 @@ Deno.test("EvmBatchBuilderLogic - batches valid input", () => { assertEquals(result?.data?.payloads[0].address, input.address); }); -Deno.test("EvmBatchBuilderLogic - skips malformed payloads", () => { +test("EvmBatchBuilderLogic - skips malformed payloads", () => { const builder = new EvmBatchBuilderLogic(); const badInput = makeInput({ input: "not-json" }); const goodInput = makeInput({ @@ -49,14 +50,14 @@ Deno.test("EvmBatchBuilderLogic - skips malformed payloads", () => { assertEquals(result?.data?.payloads[0].method, "setValue"); }); -Deno.test("EvmBatchBuilderLogic - enforces max size", () => { +test("EvmBatchBuilderLogic - enforces max size", () => { const builder = new EvmBatchBuilderLogic(); const input = makeInput(); const result = builder.buildBatchData([input], { maxSize: 10 }); assertEquals(result, { selectedInputs: [], data: null }); }); -Deno.test("EvmBatchBuilderLogic - supports hex encoded payloads", () => { +test("EvmBatchBuilderLogic - supports hex encoded payloads", () => { const builder = new EvmBatchBuilderLogic(); const json = JSON.stringify({ method: "hexCall", args: ["0x1"] }); const hexInput = makeInput({ diff --git a/packages/batcher/core/batcher.ts b/packages/batcher/core/batcher.ts index 0d8607831..440de0ecb 100644 --- a/packages/batcher/core/batcher.ts +++ b/packages/batcher/core/batcher.ts @@ -1346,11 +1346,11 @@ class SignalHandler { } catch (error) { console.error(`โŒ Error during shutdown on ${signal}:`, error); } finally { - Deno.exit(config.exitCode || 0); + process.exit(config.exitCode || 0); } }; - Deno.addSignalListener(signal as Deno.Signal, listener); + process.on(signal, listener); this.listeners.push(listener); } } diff --git a/packages/batcher/core/config.test.ts b/packages/batcher/core/config.test.ts index cb9208e6b..7064364c6 100644 --- a/packages/batcher/core/config.test.ts +++ b/packages/batcher/core/config.test.ts @@ -1,10 +1,11 @@ import { assertEquals, assertThrows } from "jsr:@std/assert"; +import { test } from "@effectstream/utils/runtime"; import { validateBatchingCriteria, type BatchingCriteriaConfig, } from "./config.ts"; -Deno.test("validateBatchingCriteria - valid time criteria", () => { +test("validateBatchingCriteria - valid time criteria", () => { const criteria: BatchingCriteriaConfig = { criteriaType: "time", timeWindowMs: 1000, @@ -12,7 +13,7 @@ Deno.test("validateBatchingCriteria - valid time criteria", () => { validateBatchingCriteria(criteria); // Should not throw }); -Deno.test("validateBatchingCriteria - invalid time criteria", () => { +test("validateBatchingCriteria - invalid time criteria", () => { const criteria: BatchingCriteriaConfig = { criteriaType: "time", }; @@ -23,7 +24,7 @@ Deno.test("validateBatchingCriteria - invalid time criteria", () => { ); }); -Deno.test("validateBatchingCriteria - valid size criteria", () => { +test("validateBatchingCriteria - valid size criteria", () => { const criteria: BatchingCriteriaConfig = { criteriaType: "size", maxBatchSize: 10, @@ -31,7 +32,7 @@ Deno.test("validateBatchingCriteria - valid size criteria", () => { validateBatchingCriteria(criteria); }); -Deno.test("validateBatchingCriteria - invalid size criteria", () => { +test("validateBatchingCriteria - invalid size criteria", () => { const criteria: BatchingCriteriaConfig = { criteriaType: "size", }; @@ -42,7 +43,7 @@ Deno.test("validateBatchingCriteria - invalid size criteria", () => { ); }); -Deno.test("validateBatchingCriteria - valid hybrid criteria", () => { +test("validateBatchingCriteria - valid hybrid criteria", () => { const criteria: BatchingCriteriaConfig = { criteriaType: "hybrid", timeWindowMs: 1000, @@ -51,7 +52,7 @@ Deno.test("validateBatchingCriteria - valid hybrid criteria", () => { validateBatchingCriteria(criteria); }); -Deno.test("validateBatchingCriteria - invalid hybrid criteria", () => { +test("validateBatchingCriteria - invalid hybrid criteria", () => { const criteria: BatchingCriteriaConfig = { criteriaType: "hybrid", timeWindowMs: 1000, diff --git a/packages/batcher/core/storage.ts b/packages/batcher/core/storage.ts index 96b6b8559..f20633a15 100644 --- a/packages/batcher/core/storage.ts +++ b/packages/batcher/core/storage.ts @@ -1,4 +1,7 @@ import type { DefaultBatcherInput } from "./types.ts"; +import { mkdirSync } from "node:fs"; +import { mkdir, readFile, writeFile, rm } from "node:fs/promises"; +import { isNotFoundError } from "@effectstream/utils/runtime"; /** * Interface for batcher storage operations @@ -54,14 +57,14 @@ export class FileStorage private readonly dataDirectory: string; constructor(dataDirectory: string = "./batcher-data") { - Deno.mkdirSync(dataDirectory, { recursive: true }); + mkdirSync(dataDirectory, { recursive: true }); this.dataDirectory = dataDirectory; this.filePath = `${dataDirectory}/pending-inputs.jsonl`; } async init(): Promise { try { - await Deno.mkdir(this.dataDirectory, { recursive: true }); + await mkdir(this.dataDirectory, { recursive: true }); } catch (error) { console.error("Error creating data directory:", error); throw new Error(`Failed to initialize storage: ${error}`); @@ -70,11 +73,7 @@ export class FileStorage async addInput(input: T): Promise { try { - await Deno.writeFile( - this.filePath, - new TextEncoder().encode(JSON.stringify(input) + "\n"), - { append: true }, - ); + await writeFile(this.filePath, JSON.stringify(input) + "\n", { flag: "a" }); } catch (error) { console.error("Error adding input to storage:", error); throw new Error(`Failed to add input: ${error}`); @@ -83,13 +82,11 @@ export class FileStorage async getAllInputs(): Promise { try { - const content = new TextDecoder().decode( - await Deno.readFile(this.filePath), - ); + const content = await readFile(this.filePath, "utf-8"); const lines = content.trim().split("\n").filter((line) => line.trim()); return lines.map((line) => JSON.parse(line)); } catch (error) { - if ((error as any).name === "NotFound") { + if (isNotFoundError(error)) { // File doesn't exist yet, return empty array return []; } @@ -117,11 +114,9 @@ export class FileStorage // Write the remaining inputs back to the file const content = remainingInputs.map((input) => JSON.stringify(input)) .join("\n"); - await Deno.writeFile( + await writeFile( this.filePath, - new TextEncoder().encode( - content + (remainingInputs.length > 0 ? "\n" : ""), - ), + content + (remainingInputs.length > 0 ? "\n" : ""), ); const removedCount = allInputs.length - remainingInputs.length; @@ -171,9 +166,9 @@ export class FileStorage async clearAllInputs(): Promise { try { - await Deno.remove(this.filePath); + await rm(this.filePath); } catch (error) { - if ((error as any).name !== "NotFound") { + if (!isNotFoundError(error)) { console.error("Error clearing inputs:", error); throw new Error(`Failed to clear inputs: ${error}`); } diff --git a/packages/build-tools/explorer/server/main.ts b/packages/build-tools/explorer/server/main.ts index f67edac05..f6debbc47 100644 --- a/packages/build-tools/explorer/server/main.ts +++ b/packages/build-tools/explorer/server/main.ts @@ -8,8 +8,8 @@ const router = new Router(); app.use(router.routes()); app.use(routeStaticFilesFrom([ - `${Deno.cwd()}/client/dist`, - `${Deno.cwd()}/client/public`, + `${process.cwd()}/client/dist`, + `${process.cwd()}/client/public`, ])); // If this is the entry point, start the server diff --git a/packages/build-tools/orchestrator/scripts/launch-avail.ts b/packages/build-tools/orchestrator/scripts/launch-avail.ts index 34f90e98f..9643511f0 100644 --- a/packages/build-tools/orchestrator/scripts/launch-avail.ts +++ b/packages/build-tools/orchestrator/scripts/launch-avail.ts @@ -1,4 +1,5 @@ import { ComponentNames } from "@effectstream/log"; +import { getEnv } from "@effectstream/utils/runtime"; // Substrate nodes (and many forks like Avail and Midnight) use the Rust tracing/log // stack wired through sc-cli/sc-service, which by default writes formatted log output to stderr. @@ -7,7 +8,9 @@ import { ComponentNames } from "@effectstream/log"; // unless the user explicitly wants to see the logs, so if EFFECTSTREAM_STDOUT is true, // we enable stderr for this as well. -const disableStderr = Deno.env.get("EFFECTSTREAM_STDOUT") === "true" ? false : true; +const isTrue = (value: string | undefined) => value != null && ["true", "1", "yes", "y"].includes(value.toLowerCase()); +const disableStderr = !isTrue(getEnv("EFFECTSTREAM_STDOUT")); + // Start Avail Node and Light Client. // diff --git a/packages/build-tools/orchestrator/scripts/launch-cardano.ts b/packages/build-tools/orchestrator/scripts/launch-cardano.ts index 5b52ce565..298c81b22 100644 --- a/packages/build-tools/orchestrator/scripts/launch-cardano.ts +++ b/packages/build-tools/orchestrator/scripts/launch-cardano.ts @@ -1,4 +1,7 @@ import { ComponentNames } from "@effectstream/log"; +import { statSync } from "node:fs"; +import { spawnSync } from "node:child_process"; +import { getEnv } from "@effectstream/utils/runtime"; // Start Cardano Node and Indexer. // @@ -28,13 +31,14 @@ export const launchCardano = (packageName: string): { cwd?: string; command?: string; }[] => { - const yaciDir = `${Deno.env.get("HOME")}/.yaci-cli`; + const home = getEnv("HOME"); + const yaciDir = `${home}/.yaci-cli`; const yaciCliPath = `${yaciDir}/yaci-cli`; // TODO $HOME/.yaci-cli/yaci-cli must be installed to allow this to work. // At the time the npm packages is incompatible with deno. try { - Deno.statSync(yaciCliPath); + statSync(yaciCliPath); } catch (_error) { throw new Error( `Cardano launcher skipped: missing ${yaciCliPath}. Run yaci-cli setup.`, @@ -43,10 +47,8 @@ export const launchCardano = (packageName: string): { // TODO We require the latest dolos binary built from source. // At the time there is not npm package for the latest dolos binary. try { - const dolosExists = new Deno.Command("deno", { - args: ["task", "-f", packageName, "dolos:exists"], - }).outputSync(); - if (!dolosExists.success) throw new Error(); + const dolosExists = spawnSync("deno", ["task", "-f", packageName, "dolos:exists"], { encoding: "utf-8" }); + if (dolosExists.status !== 0) throw new Error(); } catch (_error) { throw new Error( "Cardano launcher skipped: dolos binary is missing.", diff --git a/packages/build-tools/orchestrator/scripts/launch-midnight.ts b/packages/build-tools/orchestrator/scripts/launch-midnight.ts index b008ac76d..78bc816ec 100644 --- a/packages/build-tools/orchestrator/scripts/launch-midnight.ts +++ b/packages/build-tools/orchestrator/scripts/launch-midnight.ts @@ -1,7 +1,7 @@ import { ComponentNames } from "@effectstream/log"; +import { getEnv } from "@effectstream/utils/runtime"; import { ENV } from "@effectstream/utils/node-env"; - // Substrate nodes (and many forks like Avail and Midnight) use the Rust tracing/log // stack wired through sc-cli/sc-service, which by default writes formatted log output to stderr. // This is intentional so stdout can remain a clean data channel (e.g., for RPC JSON), @@ -9,7 +9,8 @@ import { ENV } from "@effectstream/utils/node-env"; // unless the user explicitly wants to see the logs, so if EFFECTSTREAM_STDOUT is true, // we enable stderr for this as well. -const disableStderr = Deno.env.get("EFFECTSTREAM_STDOUT") === "true" ? false : true; +const isTrue = (value: string | undefined) => value != null && ["true", "1", "yes", "y"].includes(value.toLowerCase()); +const disableStderr = !isTrue(getEnv("EFFECTSTREAM_STDOUT")); // Start Midnight Node and Indexer. // @@ -125,7 +126,7 @@ export const launchMidnight = (packageName: string): { "midnight-contract:deploy", ], env: { - MIDNIGHT_STORAGE_PASSWORD: ENV.MIDNIGHT_STORAGE_PASSWORD, + MIDNIGHT_STORAGE_PASSWORD: ENV.MIDNIGHT_STORAGE_PASSWORD }, dependsOn: [ ComponentNames.MIDNIGHT_NODE_WAIT, diff --git a/packages/build-tools/orchestrator/src/http-server.ts b/packages/build-tools/orchestrator/src/http-server.ts index 9bb0c30e3..5c9404909 100644 --- a/packages/build-tools/orchestrator/src/http-server.ts +++ b/packages/build-tools/orchestrator/src/http-server.ts @@ -80,5 +80,5 @@ try { } } catch (err) { server.log.error(err); - Deno.exit(1); + process.exit(1); } diff --git a/packages/build-tools/orchestrator/src/process.ts b/packages/build-tools/orchestrator/src/process.ts index 51e75c8ee..0b7ff32ae 100644 --- a/packages/build-tools/orchestrator/src/process.ts +++ b/packages/build-tools/orchestrator/src/process.ts @@ -2,11 +2,13 @@ import { type LogHandler, streamTo, systemLog } from "./logging.ts"; import type { Namespace } from "@effectstream/log"; import { ComponentNames } from "@effectstream/log"; import type { ValueOf } from "@effectstream/utils"; +import type { SpawnChild } from "@effectstream/utils/runtime"; +import { setExitCode, spawn } from "@effectstream/utils/runtime"; import { abortControllers } from "./start.ts"; export type ProcessComponent = { abortController: AbortController; - process: Deno.ChildProcess; + process: SpawnChild; is_piped: { stderr: boolean; stdout: boolean }; component: ValueOf; args: string[]; @@ -26,8 +28,8 @@ export class AbortProcessStart extends Error { } } -let foregroundProcess: Deno.ChildProcess | null = null; -export function setForegroundProcess(proc: Deno.ChildProcess) { +let foregroundProcess: SpawnChild | null = null; +export function setForegroundProcess(proc: SpawnChild) { foregroundProcess = proc; } @@ -68,7 +70,7 @@ export async function shutdown( "Orchestrator has shut down. Press ^C again to kill background processes that we don't currently kill automatically.", ); if (exitCode) { - Deno.exitCode = exitCode; + setExitCode(exitCode); } } @@ -119,7 +121,7 @@ export const terminateProcess = (processIndex: number) => { }; /** Send SIGTERM first, then SIGKILL after one second. */ -async function kill(proc: Deno.ChildProcess): Promise { +async function kill(proc: SpawnChild): Promise { try { proc.kill("SIGTERM"); const hardKillTimer = setTimeout( @@ -152,7 +154,7 @@ export const $ = (params: { throw new AbortProcessStart("Shutdown already called"); } const command = params.command ?? "deno"; - const process = new Deno.Command(command, { + const child = spawn(command, { args: params.args, signal: params.abortController.signal, stderr: params.stderr ?? "piped", @@ -160,11 +162,11 @@ export const $ = (params: { stdin: params.stdin ?? "inherit", env: { ...params.env, FORCE_COLOR: "true" }, cwd: params.cwd, - }).spawn(); - process.ref(); // wait until all child processes die before killing parent + }); + child.ref(); const processComponent: ProcessComponent = { - process, + process: child, abortController: params.abortController, args: [command, ...params.args], alive: true, @@ -180,7 +182,7 @@ export const $ = (params: { processes.push(processComponent); if (params.log != null) { - process.stdout.pipeTo( + child.stdout.pipeTo( streamTo( params.log, "stdout", @@ -188,7 +190,7 @@ export const $ = (params: { params.namespace ?? [], ), ); - process.stderr.pipeTo( + child.stderr.pipeTo( streamTo( params.log, "stderr", @@ -199,7 +201,7 @@ export const $ = (params: { } // note: don't block on this - void process.status.then((status) => { + void child.status.then((status) => { systemLog( `Process ${processComponent.component} (${processComponent.process.pid}) finished.\n`, ); @@ -216,9 +218,7 @@ export const $ = (params: { if (!status.success) { if (!processComponent.critical) { systemLog( - `Non-critical process ${processComponent.component} (${ - processComponent.process.pid - }) exited with status ${status.signal ?? status.code}; keeping orchestrator alive.`, + `Non-critical process ${processComponent.component} (${processComponent.process.pid}) exited with status ${status.signal ?? status.code}; keeping orchestrator alive.`, ); return; } diff --git a/packages/build-tools/orchestrator/src/start.ts b/packages/build-tools/orchestrator/src/start.ts index 45432ba32..0bee88acc 100644 --- a/packages/build-tools/orchestrator/src/start.ts +++ b/packages/build-tools/orchestrator/src/start.ts @@ -13,6 +13,7 @@ import { setStderrOutput, tsLogOrchestratorAdapter, } from "./logging.ts"; +import { spawn, setEnv, getEnv } from "@effectstream/utils/runtime"; import { $, AbortProcessStart, @@ -193,7 +194,7 @@ export async function start( ): Promise { // This is to redirect all logs.remote to the orchestrator, // where they will be redirected to the collector. - Deno.env.set("EFFECTSTREAM_ORCHESTRATOR", "true"); + setEnv("EFFECTSTREAM_ORCHESTRATOR", "true"); appConfig = config; pFactory = processFactory(config); setupLogging(config); @@ -499,9 +500,7 @@ export const processFactory = (config: OrchestratorConfigType): Record< }); void otlpCollector.process.status; - await (new Deno.Command("wait-on", { - args: [`tcp:${ENV.OTEL_COLLECTOR_PORT}`], - })).spawn().status; + await spawn("wait-on", { args: [`tcp:${ENV.OTEL_COLLECTOR_PORT}`] }).status; setCollectorStarted(ENV.OTEL_COLLECTOR_PORT); return otlpCollector; @@ -549,7 +548,7 @@ export const processFactory = (config: OrchestratorConfigType): Record< } // if EFFECTSTREAM_ENV is set, then launch the node:start:{EFFECTSTREAM_ENV} - const effectstreamEnv = Deno.env.get("EFFECTSTREAM_ENV"); + const effectstreamEnv = getEnv("EFFECTSTREAM_ENV"); const node = $({ args: ["task", effectstreamEnv ? `node:start:${effectstreamEnv}` : "node:start"], log: logHandler({}, tsLogOrchestratorAdapter), @@ -584,9 +583,7 @@ export const processFactory = (config: OrchestratorConfigType): Record< }); void paimaDb.process.status; // need to await sub-service start below - await (new Deno.Command("wait-on", { - args: [`tcp:${ENV.DB_PORT}`], - })).spawn().status; + await spawn("wait-on", { args: [`tcp:${ENV.DB_PORT}`] }).status; return paimaDb; }, diff --git a/packages/build-tools/orchestrator/src/tmux/tmux.ts b/packages/build-tools/orchestrator/src/tmux/tmux.ts index 80d46934f..ad106180b 100644 --- a/packages/build-tools/orchestrator/src/tmux/tmux.ts +++ b/packages/build-tools/orchestrator/src/tmux/tmux.ts @@ -27,6 +27,7 @@ // https://github.com/denoland/deno/issues/29904 import install_sh from "./install.sh.ts"; import session_tmux from "./session.tmux.ts"; +import { spawnOutput, type SpawnOutputResult } from "@effectstream/utils/runtime"; export interface TmuxOptions { /** @@ -45,17 +46,11 @@ export interface TmuxOptions { */ export class Tmux { static async install() { - // Pipe the built-in `install.sh` to `sh` directly. - const cmd = new Deno.Command("sh", { - stdin: "piped", + const output = await spawnOutput("sh", { + stdinInput: new TextEncoder().encode(install_sh), stdout: "piped", stderr: "piped", }); - const child = cmd.spawn(); - const writer = child.stdin.getWriter(); - await writer.write(new TextEncoder().encode(install_sh)); - await writer.close(); - const output = await child.output(); if (output.stdout.length > 0) { console.log(new TextDecoder().decode(output.stdout)); @@ -81,7 +76,7 @@ export class Tmux { /** Tell the server to start our session. */ public async startSession() { - const cmd = new Deno.Command(this.options.command, { + const output = await spawnOutput(this.options.command, { args: [ "-L", this.options.socket, @@ -90,16 +85,11 @@ export class Tmux { "source-file", "-", ], - stdin: "piped", + stdinInput: new TextEncoder().encode(session_tmux), stdout: "piped", stderr: "piped", }); - - const child = cmd.spawn(); - const writer = child.stdin.getWriter(); - await writer.write(new TextEncoder().encode(session_tmux)); - await writer.close(); - this._checkExit(await child.output()); + this._checkExit(output); } /** Attach to the session in the foreground and wait for it to be detached. */ @@ -121,17 +111,16 @@ export class Tmux { /** Kill the server, if it hasn't already exited. */ public async killServer() { - const cmd = new Deno.Command(this.options.command, { + await spawnOutput(this.options.command, { args: ["-L", this.options.socket, "-N", "kill-server"], stdin: "null", stdout: "piped", stderr: "piped", }); // Ignore exit status. We're okay with failing to kill something that isn't there. - await cmd.output(); } - private _checkExit(output: Deno.CommandOutput) { + private _checkExit(output: SpawnOutputResult) { if (!output.success) { const errorText = new TextDecoder().decode(output.stderr); throw new Error( diff --git a/packages/build-tools/tui/src/logs-standalone.ts b/packages/build-tools/tui/src/logs-standalone.ts index 8ffc4507e..a1ebeade3 100755 --- a/packages/build-tools/tui/src/logs-standalone.ts +++ b/packages/build-tools/tui/src/logs-standalone.ts @@ -3,6 +3,8 @@ import { LogServer, type OTelLog } from "./logs-server.ts"; import { createStream, type RotatingFileStream } from "rotating-file-stream"; import type { ILogObj } from "tslog"; import { ENV } from "@effectstream/utils/node-env"; +import { getEnv, spawn, exit } from "@effectstream/utils/runtime"; +import { lstatSync, mkdirSync } from "node:fs"; // This is a standalone script that can be used to view logs from the collector. // Its purpose is to be used in a tmux session, and not as a part of the TUI. @@ -14,8 +16,8 @@ class LogsViewer { constructor() { this.logServer = new LogServer(); - const calledFrom = Deno.env.get("INIT_CWD") ?? "."; - const envLogsPath = Deno.env.get("LOGS_PATH"); + const calledFrom = getEnv("INIT_CWD") ?? process.cwd(); + const envLogsPath = getEnv("LOGS_PATH"); this.logDirectory = envLogsPath ?? `${calledFrom}/logs`; } @@ -76,9 +78,9 @@ class LogsViewer { console.log("๐Ÿ” Starting log server..."); try { - Deno.lstatSync(this.logDirectory); + lstatSync(this.logDirectory); } catch (error) { - Deno.mkdirSync(this.logDirectory, { recursive: true }); + mkdirSync(this.logDirectory, { recursive: true }); } // Set up displayLogs's destination @@ -110,17 +112,16 @@ const viewer = new LogsViewer(); // so that ctrl+c terminates the entire effectstream-engine process` const killTmux = () => { if (ENV.TMUX) { - const cmd = new Deno.Command("tmux", { args: ["kill-session"] }); - cmd.spawn(); + spawn("tmux", { args: ["kill-session"], stdout: "inherit", stderr: "inherit" }); } }; -// Handle graceful shutdown -Deno.addSignalListener("SIGINT", () => { +// Handle graceful shutdown (Node-style; Deno 2 has node compat for process) +process.on("SIGINT", () => { console.log("\n๐Ÿ‘‹ Stopping log viewer..."); viewer.isRunning = false; killTmux(); - Deno.exit(0); + exit(0); }); try { @@ -128,5 +129,5 @@ try { } catch (error) { console.error(error); killTmux(); - Deno.exit(1); + exit(1); } diff --git a/packages/build-tools/tui/src/main.tsx b/packages/build-tools/tui/src/main.tsx index 122b74b45..c97198821 100644 --- a/packages/build-tools/tui/src/main.tsx +++ b/packages/build-tools/tui/src/main.tsx @@ -9,6 +9,8 @@ import { SECTION_ORDER, SECTIONS, } from "./tab/BottomBar.tsx"; +import { spawn, exit } from "@effectstream/utils/runtime"; +import { ENV } from "@effectstream/utils/node-env"; // Main App Component const App = () => { @@ -21,22 +23,23 @@ const App = () => { setRawMode(true); useEffect(() => { const handleTerminalResize = () => { - const { columns, rows } = Deno.consoleSize(); - setWidth(() => columns); - setHeight(() => rows); + setWidth(stdout.columns); + setHeight(stdout.rows); }; - Deno.addSignalListener("SIGWINCH", handleTerminalResize); + // Ink's stdout is a TTY in Node; listen for resize events where supported. + // deno-lint-ignore no-explicit-any + (stdout as any).on?.("resize", handleTerminalResize); return () => { - Deno.removeSignalListener("SIGWINCH", handleTerminalResize); + // deno-lint-ignore no-explicit-any + (stdout as any).off?.("resize", handleTerminalResize); }; - }, []); + }, [stdout]); useInput((input, key) => { if (input === "c" && key.ctrl) { - if (Deno.env.get("TMUX")) { - const cmd = new Deno.Command("tmux", { args: ["kill-session"] }); - cmd.spawn(); + if (ENV.TMUX) { + spawn("tmux", { args: ["kill-session"], stdout: "inherit", stderr: "inherit" }); } - Deno.exit(0); + exit(0); return; } // Handle left/right arrow keys for tab navigation diff --git a/packages/chains/bitcoin-contracts/src/generate-blocks.ts b/packages/chains/bitcoin-contracts/src/generate-blocks.ts index bd938d545..7e3df9ed9 100644 --- a/packages/chains/bitcoin-contracts/src/generate-blocks.ts +++ b/packages/chains/bitcoin-contracts/src/generate-blocks.ts @@ -1,13 +1,17 @@ import * as bitcoin from 'bitcoinjs-lib'; import * as ecpair from 'ecpair'; import * as ecc from 'tiny-secp256k1'; +import { args, exit } from "@effectstream/utils/runtime"; const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); const ECPair = ecpair.ECPairFactory(ecc); const SATS_PER_BTC = 100_000_000; -const DEFAULT_BLOCK_INTERVAL = Deno.args.includes('--block-interval') ? parseInt(Deno.args[Deno.args.indexOf('--block-interval') + 1]) : 5000; +const argv = args(); +const DEFAULT_BLOCK_INTERVAL = argv.includes('--block-interval') ? + parseInt(argv[argv.indexOf('--block-interval') + 1]) : + 5000; const NETWORK = bitcoin.networks.regtest; console.log(`Using block interval: ${DEFAULT_BLOCK_INTERVAL}ms`); @@ -65,18 +69,17 @@ const bitcoinRpcCall = async (method: string, params: any[] = [], walletName?: s let running = true; // Handle process signals -if (typeof Deno !== 'undefined') { - Deno.addSignalListener('SIGINT', () => { - console.log('\nReceived SIGINT, stopping block generation...'); - running = false; - Deno.exit(130); - }); - - Deno.addSignalListener('SIGTERM', () => { - console.log('\nReceived SIGTERM, stopping block generation...'); - running = false; - Deno.exit(143); - }); +const onSignal = (sig: string, code: number) => () => { + console.log(`\nReceived ${sig}, stopping block generation...`); + running = false; + exit(code); +}; +if (typeof process !== "undefined") { + process.on("SIGINT", onSignal("SIGINT", 130)); + process.on("SIGTERM", onSignal("SIGTERM", 143)); +} else if (typeof Deno !== "undefined") { + (Deno as any).addSignalListener("SIGINT", onSignal("SIGINT", 130)); + (Deno as any).addSignalListener("SIGTERM", onSignal("SIGTERM", 143)); } async function main() { @@ -121,7 +124,7 @@ async function main() { address = await bitcoinRpcCall('getnewaddress', []); } catch (e) { console.error('Failed to get address. Make sure Bitcoin Core is running and accessible.'); - Deno.exit(1); + exit(1); } } @@ -160,6 +163,6 @@ async function main() { main().catch((error) => { console.error('Fatal error:', error); - Deno.exit(1); + exit(1); }); diff --git a/packages/chains/bitcoin-contracts/src/wait-for-block.ts b/packages/chains/bitcoin-contracts/src/wait-for-block.ts index edeac9cf5..6e830d3b1 100644 --- a/packages/chains/bitcoin-contracts/src/wait-for-block.ts +++ b/packages/chains/bitcoin-contracts/src/wait-for-block.ts @@ -60,5 +60,5 @@ export async function waitForBlock(targetBlock: number) { if (import.meta.main) { await waitForBlock(100); - Deno.exit(0); + process.exit(0); } \ No newline at end of file diff --git a/packages/chains/evm-hardhat/src/builder/build.mod.ts b/packages/chains/evm-hardhat/src/builder/build.mod.ts index de3bc8eca..a78503539 100755 --- a/packages/chains/evm-hardhat/src/builder/build.mod.ts +++ b/packages/chains/evm-hardhat/src/builder/build.mod.ts @@ -190,24 +190,25 @@ async function buildModFile(): Promise { // Copy mod.ts to root and update paths to include ./build/ prefix const rootModContent = `// this is a auto-generated file. - +import fs from "node:fs"; export * from "./build/mod.ts"; export { contracts } from "./build/contracts.ts"; const __dirname = import.meta.dirname ?? ""; export const contractAddressesEvmMain: () => Record< - ${chainIds.map((chainId) => `"chain${chainId}"`).join(" | ")}, +${chainIds.map((chainId) => `"chain${chainId}"`).join(" | ")}, Record> = () => { - ${chainFiles.join("\n")} + ${chainFiles.join("\n ")} - ${chainLets.join("\n")} + ${chainLets.join("\n ")} - ${chainIds.map((chainId) => `if (typeof Deno !== 'undefined' && Deno && Deno.statSync(file${chainToIndex[`chain${chainId}`]}).isFile) { - chain${chainId} = JSON.parse(Deno.readTextFileSync(file${chainToIndex[`chain${chainId}`]})); + ${chainIds.map((chainId) => ` + if (fs.statSync(file${chainToIndex[`chain${chainId}`]}).isFile()) { + chain${chainId} = JSON.parse(fs.readFileSync(file${chainToIndex[`chain${chainId}`]}, 'utf-8')); }`).join("\n")} return { - ${chainIds.map((chainId) => `chain${chainId}`).join(",\n")} + ${chainIds.map((chainId) => `chain${chainId}`).join(",\n ")} }; } `; diff --git a/packages/chains/evm-hardhat/src/remappings/remappings-forge.ts b/packages/chains/evm-hardhat/src/remappings/remappings-forge.ts index 6e53cb452..94c191ba7 100644 --- a/packages/chains/evm-hardhat/src/remappings/remappings-forge.ts +++ b/packages/chains/evm-hardhat/src/remappings/remappings-forge.ts @@ -1,7 +1,9 @@ import { parseArgs } from "@std/cli/parse-args"; +import { writeFileSync } from "node:fs"; +import { args } from "@effectstream/utils/runtime"; // The depth is the number of directories where the node_modules are located. -const flags = parseArgs(Deno.args, { +const flags = parseArgs(args(), { string: ['depth', "package"], default: { depth: '4', package: '@effectstream' }, }); @@ -11,7 +13,7 @@ const packageName = flags.package; if (isNaN(depth) || depth < 0) { console.error('Error: --depth must be a non-negative number.'); - Deno.exit(1); + process.exit(1); } const prefix = '../'.repeat(depth); @@ -22,6 +24,6 @@ const remappings = `${packageName}/=${prefix}node_modules/${packageName}/`, ].join('\n') + '\n'; -Deno.writeTextFileSync("./remappings.txt", remappings); +writeFileSync("./remappings.txt", remappings); console.log(`Wrote forge remappings to remappings.txt`); diff --git a/packages/chains/evm-hardhat/src/remappings/remappings-hardhat.ts b/packages/chains/evm-hardhat/src/remappings/remappings-hardhat.ts index bc1b743fa..8d783a48e 100644 --- a/packages/chains/evm-hardhat/src/remappings/remappings-hardhat.ts +++ b/packages/chains/evm-hardhat/src/remappings/remappings-hardhat.ts @@ -1,9 +1,11 @@ // Remappings for hardhat / forge are not compatible. // So we need to create them depending on the tool we are using. import { parseArgs } from "@std/cli/parse-args"; +import { writeFileSync } from "node:fs"; +import { args } from "@effectstream/utils/runtime"; // The depth is the number of directories where the node_modules are located. -const flags = parseArgs(Deno.args, { +const flags = parseArgs(args(), { string: ["package"], default: { package: '@paima' }, }); @@ -14,4 +16,4 @@ import * as remappings from "./assets.ts"; const hardhatRemappings = new TextDecoder().decode(remappings.default.files["remappings.hardhat"].content) .replace(/@effectstream/g, packageName); -Deno.writeTextFileSync("./remappings.txt", hardhatRemappings); \ No newline at end of file +writeFileSync("./remappings.txt", hardhatRemappings); \ No newline at end of file diff --git a/packages/chains/midnight-contracts/src/build-wallet.ts b/packages/chains/midnight-contracts/src/build-wallet.ts index 5649aa2fe..fa9d84a00 100644 --- a/packages/chains/midnight-contracts/src/build-wallet.ts +++ b/packages/chains/midnight-contracts/src/build-wallet.ts @@ -15,6 +15,7 @@ import { import type { WalletResult, NetworkUrls } from "./types.ts"; import type { InitialOwner } from "./types.ts"; +import { getEnv } from "@effectstream/utils/runtime"; // ============================================================================ // Wallet Facade @@ -42,7 +43,7 @@ export async function buildWalletAndWaitForFunds( const syncTimeoutMs = resolveWalletSyncTimeoutMs(); if (balance === 0n) { const skipWait = - Deno.env.get("MIDNIGHT_SKIP_WAIT_FOR_FUNDS")?.toLowerCase() === "true"; + getEnv("MIDNIGHT_SKIP_WAIT_FOR_FUNDS")?.toLowerCase() === "true"; log.info("Wallet shielded balance: 0"); log.info( `Waiting to receive tokens... (timeout ${syncTimeoutMs}ms${skipWait ? ", skip on timeout enabled" : ""})` diff --git a/packages/chains/midnight-contracts/src/deploy.ts b/packages/chains/midnight-contracts/src/deploy.ts index b002cea31..6b42bad0a 100644 --- a/packages/chains/midnight-contracts/src/deploy.ts +++ b/packages/chains/midnight-contracts/src/deploy.ts @@ -17,12 +17,15 @@ import type{ NetworkUrls, DeployConfig, WalletResult } from "./types.ts"; import { buildWalletAndWaitForFunds, extractInitialOwnerFromWallet } from "./build-wallet.ts"; import { configureMidnightNodeProviders } from "./providers.ts"; import { midnightNetworkConfig } from "./midnight-env.ts"; +import { getEnv, cwd } from "@effectstream/utils/runtime"; +import { readdirSync, statSync } from "node:fs"; +import { writeFile } from "node:fs/promises"; // Declare Deno global for type-checking when not executed under Deno tooling. declare const Deno: typeof globalThis.Deno; function checkEnvVariables(): void { - if (!Deno.env.get("MIDNIGHT_STORAGE_PASSWORD")) { + if (!getEnv("MIDNIGHT_STORAGE_PASSWORD")) { throw new Error("MIDNIGHT_STORAGE_PASSWORD is not set (Use a 16 char string)"); } } @@ -34,8 +37,8 @@ function hasManagedArtifacts(dir: string): boolean { const requiredDirs = ["contract", "compiler"]; try { return requiredDirs.every((name) => { - const stats = Deno.statSync(path.join(dir, name)); - return stats.isDirectory; + const stats = statSync(path.join(dir, name)); + return stats.isDirectory(); }); } catch { return false; @@ -44,8 +47,8 @@ function hasManagedArtifacts(dir: string): boolean { function findCompilerSubdirectory(managedDir: string): string { try { - for (const entry of Deno.readDirSync(managedDir)) { - if (!entry.isDirectory) continue; + for (const entry of readdirSync(managedDir, { withFileTypes: true })) { + if (!entry.isDirectory()) continue; const candidate = path.join(managedDir, entry.name); if (hasManagedArtifacts(candidate)) { return entry.name; @@ -107,7 +110,7 @@ export async function deployMidnightContract( if (!contractDir) { throw new Error( `Could not find Midnight contract directory for "${config.contractName}". ` + - `Searched starting from ${config.baseDir || Deno.cwd()}. ` + + `Searched starting from ${config.baseDir || cwd()}. ` + `Please ensure you're running from a directory that contains or is a parent of the Midnight contract directory, ` + `or provide an explicit baseDir parameter.` ); @@ -180,7 +183,7 @@ export async function deployMidnightContract( unshieldedKeystore, } = walletResult; const resolvedDustReceiverAddress = - Deno.env.get("MIDNIGHT_DUST_RECEIVER_ADDRESS") ?? dustAddress; + getEnv("MIDNIGHT_DUST_RECEIVER_ADDRESS") ?? dustAddress; if (resolvedDustReceiverAddress === dustAddress) { log.info(`Using derived dust address: ${resolvedDustReceiverAddress}`); } else { @@ -271,7 +274,7 @@ export async function deployMidnightContract( outputFileName, ); - await Deno.writeTextFile( + await writeFile( outputPath, JSON.stringify({ contractAddress }, null, 2), ); diff --git a/packages/chains/midnight-contracts/src/get-wallet-info.ts b/packages/chains/midnight-contracts/src/get-wallet-info.ts index 3d98b3e4f..c7a8c7770 100644 --- a/packages/chains/midnight-contracts/src/get-wallet-info.ts +++ b/packages/chains/midnight-contracts/src/get-wallet-info.ts @@ -26,6 +26,8 @@ import type { DefaultV1Configuration } from "@midnight-ntwrk/wallet-sdk-shielded import { CONSTANTS } from "./constants.ts"; import type { NetworkUrls, WalletResult } from "./types.ts"; import { midnightNetworkConfig } from "./midnight-env.ts"; +import { exit } from "node:process"; +import { getEnv, args, isNotFoundError } from "@effectstream/utils/runtime"; // ============================================================================ // Key Derivation @@ -61,7 +63,7 @@ function deriveSeedForRole(seed: string, role: DerivationRole): Uint8Array { * Resolve sync timeout from env or default. */ export function resolveWalletSyncTimeoutMs(): number { - const envValue = Deno.env.get("MIDNIGHT_WALLET_SYNC_TIMEOUT_MS"); + const envValue = getEnv("MIDNIGHT_WALLET_SYNC_TIMEOUT_MS"); if (!envValue) return CONSTANTS.WALLET_SYNC_TIMEOUT_MS; const parsed = Number(envValue); if (Number.isFinite(parsed) && parsed > 0) return parsed; @@ -519,7 +521,7 @@ async function main() { }); // Parse command-line arguments - const args = parseArgs(Deno.args, { + const parsedArgs = parseArgs(args(), { string: ["seed"], boolean: ["balance"], default: { @@ -531,13 +533,13 @@ async function main() { try { await load({ envPath: ".env.testnet", export: true }); } catch (error) { - if (!(error instanceof Deno.errors.NotFound)) { + if (!isNotFoundError(error)) { log.warn(`Failed to load .env.testnet: ${error}`); } } - const envSeed = Deno.env.get("MIDNIGHT_WALLET_SEED"); - const argSeed = args.seed; + const envSeed = getEnv("MIDNIGHT_WALLET_SEED"); + const argSeed = parsedArgs.seed; let seed = argSeed || envSeed; if (!seed) { @@ -564,11 +566,11 @@ async function main() { } // Network Configuration - const indexer = Deno.env.get("MIDNIGHT_INDEXER_URL") || midnightNetworkConfig.indexer; - const indexerWS = Deno.env.get("MIDNIGHT_INDEXER_WS_URL") || midnightNetworkConfig.indexerWS; - const node = Deno.env.get("MIDNIGHT_NODE_URL") || midnightNetworkConfig.node; - const proofServer = Deno.env.get("MIDNIGHT_PROOF_SERVER_URL") || midnightNetworkConfig.proofServer; - + const indexer = getEnv("MIDNIGHT_INDEXER_URL") || midnightNetworkConfig.indexer; + const indexerWS = getEnv("MIDNIGHT_INDEXER_WS_URL") || midnightNetworkConfig.indexerWS; + const node = getEnv("MIDNIGHT_NODE_URL") || midnightNetworkConfig.node; + const proofServer = getEnv("MIDNIGHT_PROOF_SERVER_URL") || midnightNetworkConfig.proofServer; + const networkUrls: Required = { id: "placeholder-value", indexer, @@ -577,7 +579,7 @@ async function main() { proofServer }; - const networkIdRaw = Deno.env.get("MIDNIGHT_NETWORK_ID") || "undeployed"; + const networkIdRaw = getEnv("MIDNIGHT_NETWORK_ID") || "undeployed"; // Map common network names to NetworkId enum values // Based on midnight-js testkit examples and Lace wallet compatibility @@ -627,7 +629,7 @@ async function main() { log.info("=========================================="); // Only fetch balances if --balance flag is provided - if (args.balance) { + if (parsedArgs.balance) { log.info("=========================================="); log.info("Fetching Balances..."); log.info("=========================================="); @@ -721,7 +723,7 @@ async function main() { await walletResult.wallet.stop(); } catch (error) { log.error(`Error: ${error instanceof Error ? error.message : String(error)}`); - Deno.exit(1); + exit(1); } } diff --git a/packages/chains/midnight-contracts/src/midnight-env.ts b/packages/chains/midnight-contracts/src/midnight-env.ts index 0a6fd0069..e9d01293a 100644 --- a/packages/chains/midnight-contracts/src/midnight-env.ts +++ b/packages/chains/midnight-contracts/src/midnight-env.ts @@ -1,28 +1,11 @@ import type { NetworkId } from "@midnight-ntwrk/wallet-sdk-abstractions"; import { mnemonicToSeed } from "@scure/bip39"; import { Buffer } from "node:buffer"; +import { getEnv } from "@effectstream/utils/runtime"; const getEnvValue = (key: string): string | undefined => { - try { - // @ts-ignore: Deno global check - if (typeof Deno !== "undefined" && Deno?.env?.get) { - // @ts-ignore: Deno global access - return Deno.env.get(key); - } - } catch (_e) { /* ignore */ } - - try { - // @ts-ignore: Node/Process global check - if (typeof process !== "undefined" && process?.env) { - // @ts-ignore: Node/Process global access - return process.env[key]; - } - } catch (_e) { /* ignore */ } - - // Fallback for some Vite setups if process.env isn't shimmed but replace is used - // Note: Direct import.meta.env usage is avoided to prevent TS/Build errors in non-ESM targets - return undefined; -} + return getEnv(key); +}; const env = (key: string | string[], fallback?: string): string => { if (typeof key === 'string') { diff --git a/packages/chains/midnight-contracts/src/read-contract.ts b/packages/chains/midnight-contracts/src/read-contract.ts index 7d346a874..197a8cf47 100644 --- a/packages/chains/midnight-contracts/src/read-contract.ts +++ b/packages/chains/midnight-contracts/src/read-contract.ts @@ -1,6 +1,8 @@ // TODO Remove references to "src/managed" as this is not standard. import * as path from "@std/path"; +import { readdirSync, statSync, readFileSync } from "node:fs"; +import { getEnv, cwd, isNotFoundError, getRuntime } from "@effectstream/utils/runtime"; /** * Information about a compiled Midnight contract @@ -59,11 +61,11 @@ function findAllFilesRecursive( } try { - const entries = Array.from(Deno.readDirSync(dir)); + const entries = readdirSync(dir, { withFileTypes: true }); // First check if the file exists in the current directory - const hasFile = entries.some(entry => - entry.isFile && entry.name === fileName + const hasFile = entries.some(entry => + entry.isFile() && entry.name === fileName ); if (hasFile) { @@ -72,7 +74,7 @@ function findAllFilesRecursive( // Then recursively search subdirectories for (const entry of entries) { - if (entry.isDirectory) { + if (entry.isDirectory()) { // Skip common directories that are unlikely to contain contracts const skipDirs = ["node_modules", ".git", "dist", "build", ".deno"]; if (skipDirs.includes(entry.name)) { @@ -98,8 +100,8 @@ function findAllFilesRecursive( function isValidMidnightContractDir(dir: string, contractName: string): boolean { const managedDir = path.join(dir, contractName, "src/managed"); try { - const stats = Deno.statSync(managedDir); - return stats.isDirectory; + const stats = statSync(managedDir); + return stats.isDirectory(); } catch { return false; } @@ -112,8 +114,8 @@ function hasManagedArtifacts(dir: string): boolean { const requiredDirs = ["compiler", "contract"]; try { return requiredDirs.every((entry) => { - const stats = Deno.statSync(path.join(dir, entry)); - return stats.isDirectory; + const stats = statSync(path.join(dir, entry)); + return stats.isDirectory(); }); } catch { return false; @@ -127,8 +129,8 @@ function hasManagedArtifacts(dir: string): boolean { */ function resolveManagedArtifactsDir(managedDir: string): string { try { - for (const entry of Deno.readDirSync(managedDir)) { - if (!entry.isDirectory) continue; + for (const entry of readdirSync(managedDir, { withFileTypes: true })) { + if (!entry.isDirectory()) continue; const candidate = path.join(managedDir, entry.name); if (hasManagedArtifacts(candidate)) { return candidate; @@ -163,7 +165,7 @@ export function findContractDirectoryForDeploy( if (baseDir) { startDir = path.resolve(baseDir); } else { - startDir = Deno.cwd(); + startDir = cwd(); } let currentDir = path.resolve(startDir); @@ -171,11 +173,11 @@ export function findContractDirectoryForDeploy( while (currentDir !== root) { try { - const entries = Array.from(Deno.readDirSync(currentDir)); + const entries = readdirSync(currentDir, { withFileTypes: true }); // Check if contract directory exists here for (const entry of entries) { - if (entry.isDirectory && entry.name === contractName) { + if (entry.isDirectory() && entry.name === contractName) { // Validate it's a Midnight contract by checking for src/managed/ if (isValidMidnightContractDir(currentDir, contractName)) { return currentDir; @@ -185,7 +187,7 @@ export function findContractDirectoryForDeploy( // Also search recursively in subdirectories (up to 3 levels deep) for (const entry of entries) { - if (entry.isDirectory) { + if (entry.isDirectory()) { const skipDirs = ["node_modules", ".git", "dist", "build", ".deno"]; if (skipDirs.includes(entry.name)) { continue; @@ -193,11 +195,11 @@ export function findContractDirectoryForDeploy( try { const subDir = path.join(currentDir, entry.name); - const subEntries = Array.from(Deno.readDirSync(subDir)); + const subEntries = readdirSync(subDir, { withFileTypes: true }); // Check direct subdirectory for (const subEntry of subEntries) { - if (subEntry.isDirectory && subEntry.name === contractName) { + if (subEntry.isDirectory() && subEntry.name === contractName) { if (isValidMidnightContractDir(subDir, contractName)) { return subDir; } @@ -206,12 +208,12 @@ export function findContractDirectoryForDeploy( // Check one more level deep for (const subEntry of subEntries) { - if (subEntry.isDirectory && !skipDirs.includes(subEntry.name)) { + if (subEntry.isDirectory() && !skipDirs.includes(subEntry.name)) { const subSubDir = path.join(subDir, subEntry.name); try { - const subSubEntries = Array.from(Deno.readDirSync(subSubDir)); + const subSubEntries = readdirSync(subSubDir, { withFileTypes: true }); for (const subSubEntry of subSubEntries) { - if (subSubEntry.isDirectory && subSubEntry.name === contractName) { + if (subSubEntry.isDirectory() && subSubEntry.name === contractName) { if (isValidMidnightContractDir(subSubDir, contractName)) { return subSubDir; } @@ -301,11 +303,11 @@ export function readMidnightContract( if (baseDir) { moduleDir = path.resolve(baseDir); - } else if (typeof Deno !== "undefined") { + } else if (getRuntime().environment === 'backend') { let foundDir: string | null = null; for (const candidate of candidateFileNames) { - const dir = findContractDirectory(Deno.cwd(), candidate, contractName); + const dir = findContractDirectory(cwd(), candidate, contractName); if (dir) { foundDir = dir; break; @@ -315,7 +317,7 @@ export function readMidnightContract( if (!foundDir) { throw new Error( `Could not find Midnight contract directory for "${contractName}". ` + - `Searched for ${candidateFileNames.join(", ")} starting from ${path.resolve(Deno.cwd())}. ` + + `Searched for ${candidateFileNames.join(", ")} starting from ${path.resolve(cwd())}. ` + `Please ensure you're running from a directory that contains or is a parent of the Midnight contract files, ` + `or provide an explicit baseDir parameter. ` + `Note: This function only finds Midnight contracts (with src/managed/ structure), not EVM contracts.` @@ -324,16 +326,13 @@ export function readMidnightContract( moduleDir = foundDir; } else { - const envContractAddress = - typeof Deno !== "undefined" - ? Deno.env.get("MIDNIGHT_CONTRACT_ADDRESS") - : undefined; + const envContractAddress = getEnv("MIDNIGHT_CONTRACT_ADDRESS"); return { contractAddress: envContractAddress || "", contractInfo: { circuits: [], witnesses: [], contracts: [] }, zkConfigPath: "", contractDir: "", - }; + }; } const normalizedModuleDir = path.resolve(moduleDir); @@ -348,11 +347,11 @@ export function readMidnightContract( for (const candidate of candidateFileNames) { const candidatePath = path.join(moduleDir, candidate); try { - contractAddressJson = Deno.readTextFileSync(candidatePath); + contractAddressJson = readFileSync(candidatePath, "utf-8"); actualContractFileName = candidate; break; } catch (err) { - if (err instanceof Deno.errors.NotFound) { + if (isNotFoundError(err)) { continue; } throw err; @@ -385,7 +384,7 @@ export function readMidnightContract( "compiler/contract-info.json" ); const zkConfigPath = path.resolve(managedArtifactsDir); - const contractInfoJson = Deno.readTextFileSync(contractInfoPath); + const contractInfoJson = readFileSync(contractInfoPath, "utf-8"); const contractAddressInfo = JSON.parse(contractAddressJson) as MidnightContractAddressInfo; const contractInfo = JSON.parse(contractInfoJson) as MidnightContractCompilerInfo; @@ -410,7 +409,7 @@ export function readMidnightContract( } // Override contract address if MIDNIGHT_CONTRACT_ADDRESS env var is set - const envContractAddress = Deno.env.get("MIDNIGHT_CONTRACT_ADDRESS"); + const envContractAddress = getEnv("MIDNIGHT_CONTRACT_ADDRESS"); if (envContractAddress) { contractAddress = envContractAddress; contractAddresses[resolvedNetworkId] = envContractAddress; @@ -426,7 +425,7 @@ export function readMidnightContract( return cachedContractInfo[cacheKey]; } catch (err) { - if (err instanceof Deno.errors.NotFound) { + if (isNotFoundError(err)) { const fileList = candidateFileNames .map((name) => path.join(moduleDir, name)) .join(", "); diff --git a/packages/effectstream-sdk/concise/src/batcher.test.ts b/packages/effectstream-sdk/concise/src/batcher.test.ts index 9e6d72ba2..00f5ddcea 100644 --- a/packages/effectstream-sdk/concise/src/batcher.test.ts +++ b/packages/effectstream-sdk/concise/src/batcher.test.ts @@ -1,4 +1,5 @@ import { assertEquals, assertThrows } from "jsr:@std/assert"; +import { test } from "@effectstream/utils/runtime"; import { AddressType, type TimestampMsStr, @@ -15,7 +16,7 @@ const MOCK_TIMESTAMP = "1234567890000" as TimestampMsStr; const MOCK_SIGNATURE = "0xsignature"; const MOCK_INPUT = "some-input"; -Deno.test("createBatcherSubunit - creates valid subunit for EVM", () => { +test("createBatcherSubunit - creates valid subunit for EVM", () => { const subunit = createBatcherSubunit( MOCK_TIMESTAMP, MOCK_ADDRESS, @@ -31,7 +32,7 @@ Deno.test("createBatcherSubunit - creates valid subunit for EVM", () => { assertEquals(subunit.timestamp, MOCK_TIMESTAMP); }); -Deno.test("createBatcherSubunit - throws for unsupported address type", () => { +test("createBatcherSubunit - throws for unsupported address type", () => { assertThrows( () => { createBatcherSubunit( @@ -47,7 +48,7 @@ Deno.test("createBatcherSubunit - throws for unsupported address type", () => { ); }); -Deno.test("createMessageForBatcher - creates valid message", () => { +test("createMessageForBatcher - creates valid message", () => { const msg = createMessageForBatcher( "namespace", MOCK_TIMESTAMP, @@ -72,7 +73,7 @@ Deno.test("createMessageForBatcher - creates valid message", () => { assertEquals(msg.includes(MOCK_INPUT.toLowerCase()), true); }); -Deno.test("hashBatchSubunit - returns hash starting with 0x", () => { +test("hashBatchSubunit - returns hash starting with 0x", () => { const subunit = createBatcherSubunit( MOCK_TIMESTAMP, MOCK_ADDRESS, @@ -86,7 +87,7 @@ Deno.test("hashBatchSubunit - returns hash starting with 0x", () => { assertEquals(hash.length > 10, true); }); -Deno.test("hashBatchSubunit - throws for unsupported address type", () => { +test("hashBatchSubunit - throws for unsupported address type", () => { const subunit = { addressType: 999 as AddressType, address: MOCK_ADDRESS, diff --git a/packages/effectstream-sdk/concise/src/delegate.test.ts b/packages/effectstream-sdk/concise/src/delegate.test.ts index 0ca87cf84..2573eee23 100644 --- a/packages/effectstream-sdk/concise/src/delegate.test.ts +++ b/packages/effectstream-sdk/concise/src/delegate.test.ts @@ -1,26 +1,27 @@ import { assertEquals } from "jsr:@std/assert"; +import { test } from "@effectstream/utils/runtime"; import { accountMessages } from "./delegate.ts"; import type { WalletAddress } from "@effectstream/utils"; const MOCK_ADDRESS = "0x1234567890123456789012345678901234567890" as WalletAddress; const MOCK_ADDRESS_2 = "0x0987654321098765432109876543210987654321" as WalletAddress; -Deno.test("accountMessages.linkAccount - formats correctly", () => { +test("accountMessages.linkAccount - formats correctly", () => { const msg = accountMessages.linkAccount(1, MOCK_ADDRESS, true); assertEquals(msg, `link:1:${MOCK_ADDRESS}:true`); }); -Deno.test("accountMessages.linkAccount - formats correctly with false", () => { +test("accountMessages.linkAccount - formats correctly with false", () => { const msg = accountMessages.linkAccount(1, MOCK_ADDRESS, false); assertEquals(msg, `link:1:${MOCK_ADDRESS}:false`); }); -Deno.test("accountMessages.unlinkAccountWithPrimary - formats correctly without new primary", () => { +test("accountMessages.unlinkAccountWithPrimary - formats correctly without new primary", () => { const msg = accountMessages.unlinkAccountWithPrimary(1, MOCK_ADDRESS); assertEquals(msg, `unlink:1:${MOCK_ADDRESS}:`); }); -Deno.test("accountMessages.unlinkAccountWithPrimary - formats correctly with new primary", () => { +test("accountMessages.unlinkAccountWithPrimary - formats correctly with new primary", () => { const msg = accountMessages.unlinkAccountWithPrimary(1, MOCK_ADDRESS, MOCK_ADDRESS_2); assertEquals(msg, `unlink:1:${MOCK_ADDRESS}:${MOCK_ADDRESS_2}`); }); diff --git a/packages/effectstream-sdk/config/src/config/utils.test.ts b/packages/effectstream-sdk/config/src/config/utils.test.ts index 3055a874b..de4f25050 100644 --- a/packages/effectstream-sdk/config/src/config/utils.test.ts +++ b/packages/effectstream-sdk/config/src/config/utils.test.ts @@ -1,4 +1,5 @@ import { assertEquals } from "jsr:@std/assert"; +import { test } from "@effectstream/utils/runtime"; import { getPrimitivesForSyncProtocol, onlyOnce, @@ -9,7 +10,7 @@ import type { ConfigSyncProtocolType, } from "../schema/sync-protocols/types.ts"; -Deno.test("onlyOnce - returns built value", () => { +test("onlyOnce - returns built value", () => { const expected = { some: "value" }; const result = onlyOnce({ key: () => undefined, @@ -18,7 +19,7 @@ Deno.test("onlyOnce - returns built value", () => { assertEquals(result, expected); }); -Deno.test("onlyNotError - returns built value", () => { +test("onlyNotError - returns built value", () => { const expected = { some: "value" }; const result = onlyNotError({ key: () => undefined, @@ -27,7 +28,7 @@ Deno.test("onlyNotError - returns built value", () => { assertEquals(result, expected); }); -Deno.test("onlyValue - returns built value when targets match", () => { +test("onlyValue - returns built value when targets match", () => { const expected = { some: "value" }; const result = onlyValue({ value: () => "test", @@ -37,7 +38,7 @@ Deno.test("onlyValue - returns built value when targets match", () => { assertEquals(result, expected); }); -Deno.test("getPrimitivesForSyncProtocol - filters primitives correctly", () => { +test("getPrimitivesForSyncProtocol - filters primitives correctly", () => { const primitives = { "prim1": { syncProtocol: "evm", primitive: {} }, "prim2": { syncProtocol: "cardano", primitive: {} }, @@ -53,7 +54,7 @@ Deno.test("getPrimitivesForSyncProtocol - filters primitives correctly", () => { assertEquals(result[0].syncProtocol, "evm"); }); -Deno.test("getPrimitivesForSyncProtocol - returns empty array for no matches", () => { +test("getPrimitivesForSyncProtocol - returns empty array for no matches", () => { const primitives = { "prim1": { syncProtocol: "evm", primitive: {} }, }; @@ -64,7 +65,7 @@ Deno.test("getPrimitivesForSyncProtocol - returns empty array for no matches", ( assertEquals(result.length, 0); }); -Deno.test("getPrimitivesForSyncProtocol - handles empty primitives", () => { +test("getPrimitivesForSyncProtocol - handles empty primitives", () => { // @ts-ignore: Testing internal logic const result = getPrimitivesForSyncProtocol({}, "evm"); assertEquals(result.length, 0); diff --git a/packages/effectstream-sdk/crypto/src/Prando.test.ts b/packages/effectstream-sdk/crypto/src/Prando.test.ts index 22d67762e..cc9f369f9 100644 --- a/packages/effectstream-sdk/crypto/src/Prando.test.ts +++ b/packages/effectstream-sdk/crypto/src/Prando.test.ts @@ -1,7 +1,8 @@ import { assertEquals } from "jsr:@std/assert"; import { Prando } from "./Prando.ts"; +import { test } from "@effectstream/utils/runtime"; -Deno.test("Prando - Deterministic sequence", () => { +test("Prando - Deterministic sequence", () => { const seed = 12345; const rng1 = new Prando(seed); const rng2 = new Prando(seed); @@ -11,7 +12,7 @@ Deno.test("Prando - Deterministic sequence", () => { assertEquals(rng1.nextString(5), rng2.nextString(5)); }); -Deno.test("Prando - Different seeds produce different sequences", () => { +test("Prando - Different seeds produce different sequences", () => { const rng1 = new Prando(12345); const rng2 = new Prando(67890); @@ -25,7 +26,7 @@ Deno.test("Prando - Different seeds produce different sequences", () => { } }); -Deno.test("Prando - Reset works", () => { +test("Prando - Reset works", () => { const seed = 98765; const rng = new Prando(seed); const val1 = rng.next(); diff --git a/packages/effectstream-sdk/crypto/src/paima-hash.test.ts b/packages/effectstream-sdk/crypto/src/paima-hash.test.ts index 68e4e99e3..78175eeeb 100644 --- a/packages/effectstream-sdk/crypto/src/paima-hash.test.ts +++ b/packages/effectstream-sdk/crypto/src/paima-hash.test.ts @@ -1,8 +1,9 @@ import { assertEquals } from "jsr:@std/assert"; import { generatePaimaBlockHash } from "./paima-hash.ts"; import type { BlockHash, PaimaBlockHash } from "@effectstream/utils"; +import { test } from "@effectstream/utils/runtime"; -Deno.test("generatePaimaBlockHash - generates hash correctly", () => { +test("generatePaimaBlockHash - generates hash correctly", () => { const mockBlock = { blockInfo: [ { blockHash: "0x123" as BlockHash }, @@ -17,7 +18,7 @@ Deno.test("generatePaimaBlockHash - generates hash correctly", () => { assertEquals(result.startsWith("0x"), true); }); -Deno.test("generatePaimaBlockHash - handles null previous hash", () => { +test("generatePaimaBlockHash - handles null previous hash", () => { const mockBlock = { blockInfo: [ { blockHash: "0x123" as BlockHash } diff --git a/packages/effectstream-sdk/log/src/mod.ts b/packages/effectstream-sdk/log/src/mod.ts index 3a69be197..34a88d38e 100644 --- a/packages/effectstream-sdk/log/src/mod.ts +++ b/packages/effectstream-sdk/log/src/mod.ts @@ -2,6 +2,7 @@ import { SeverityNumber } from "@opentelemetry/api-logs"; import { tsLogFormatted, tsLogGetFormattedMessage, tsLogOrchestrator, type TslogLogFunc } from "./tslog.ts"; import { otelLog, type OtelLogFunc } from "./otel/logger.ts"; import "./brands.ts"; // register material-chalk brands +import { getEnv } from "@effectstream/utils/runtime"; export * from "./otel/setup.ts"; export { ComponentNames, @@ -15,8 +16,9 @@ export { DefaultLogLevels } from "./tslog.ts"; // so that we don't need to re-import opentelemetry in every component export { SeverityNumber }; -const localLogger = Deno && Deno.env.get("EFFECTSTREAM_ORCHESTRATOR") ? tsLogOrchestrator : tsLogFormatted; -const remoteLogger = Deno && Deno.env.get("EFFECTSTREAM_ORCHESTRATOR") ? tsLogOrchestrator : otelLog; +const orchestratorEnv = getEnv("EFFECTSTREAM_ORCHESTRATOR"); +const localLogger = orchestratorEnv ? tsLogOrchestrator : tsLogFormatted; +const remoteLogger = orchestratorEnv ? tsLogOrchestrator : otelLog; export const log: { local: TslogLogFunc; diff --git a/packages/effectstream-sdk/utils/deno.json b/packages/effectstream-sdk/utils/deno.json index 6ed2db0b3..dbe5a9319 100644 --- a/packages/effectstream-sdk/utils/deno.json +++ b/packages/effectstream-sdk/utils/deno.json @@ -4,7 +4,8 @@ "license": "MIT", "exports": { ".": "./src/mod.ts", - "./node-env": "./src/config.ts" + "./node-env": "./src/config.ts", + "./runtime": "./src/runtime.ts" }, "imports": { "@coderspirit/nominal": "npm:@coderspirit/nominal@^4.1.1", @@ -23,4 +24,4 @@ "js-base64": "npm:js-base64@^3.7.7", "js-sha512": "npm:js-sha512@^0.9.0" } -} \ No newline at end of file +} diff --git a/packages/effectstream-sdk/utils/src/binary-search.test.ts b/packages/effectstream-sdk/utils/src/binary-search.test.ts index 55a08ebcf..340ddab42 100644 --- a/packages/effectstream-sdk/utils/src/binary-search.test.ts +++ b/packages/effectstream-sdk/utils/src/binary-search.test.ts @@ -1,37 +1,38 @@ import { assertEquals } from "jsr:@std/assert"; +import { test } from "@effectstream/utils/runtime"; import { binarySearch } from "./binary-search.ts"; -Deno.test("binarySearch - finds exact match", () => { +test("binarySearch - finds exact match", () => { const arr = [1, 3, 5, 7, 9]; const result = binarySearch(arr, 5, (val) => val); assertEquals(result, 2); }); -Deno.test("binarySearch - finds closest value >= target", () => { +test("binarySearch - finds closest value >= target", () => { const arr = [1, 3, 5, 7, 9]; const result = binarySearch(arr, 6, (val) => val); assertEquals(result, 3); }); -Deno.test("binarySearch - returns 0 if target is smaller than all elements", () => { +test("binarySearch - returns 0 if target is smaller than all elements", () => { const arr = [1, 3, 5, 7, 9]; const result = binarySearch(arr, 0, (val) => val); assertEquals(result, 0); }); -Deno.test("binarySearch - returns undefined if target is larger than all elements", () => { +test("binarySearch - returns undefined if target is larger than all elements", () => { const arr = [1, 3, 5, 7, 9]; const result = binarySearch(arr, 10, (val) => val); assertEquals(result, undefined); }); -Deno.test("binarySearch - returns undefined for empty array", () => { +test("binarySearch - returns undefined for empty array", () => { const arr: number[] = []; const result = binarySearch(arr, 5, (val) => val); assertEquals(result, undefined); }); -Deno.test("binarySearch - works with custom projection function", () => { +test("binarySearch - works with custom projection function", () => { const arr = [{ id: 10 }, { id: 20 }, { id: 30 }]; const result = binarySearch(arr, 20, (val) => val.id); assertEquals(result, 1); diff --git a/packages/effectstream-sdk/utils/src/concurrency/latch.test.ts b/packages/effectstream-sdk/utils/src/concurrency/latch.test.ts index 9fad1b3b9..467377ef9 100644 --- a/packages/effectstream-sdk/utils/src/concurrency/latch.test.ts +++ b/packages/effectstream-sdk/utils/src/concurrency/latch.test.ts @@ -1,8 +1,9 @@ import { assertEquals, assertRejects } from "jsr:@std/assert"; +import { test } from "@effectstream/utils/runtime"; import { countdownLatch } from "./latch.ts"; import { run, sleep, spawn } from "effection"; -Deno.test("countdownLatch - waits for countdown", async () => { +test("countdownLatch - waits for countdown", async () => { await run(function* () { const { wait, countDown } = countdownLatch(2); let done = false; @@ -25,7 +26,7 @@ Deno.test("countdownLatch - waits for countdown", async () => { }); }); -Deno.test("countdownLatch - respects timeout", async () => { +test("countdownLatch - respects timeout", async () => { await assertRejects(async () => { await run(function* () { const { wait } = countdownLatch(1); @@ -34,7 +35,7 @@ Deno.test("countdownLatch - respects timeout", async () => { }, Error, "timeout"); }); -Deno.test("countdownLatch - no-op if already zero", async () => { +test("countdownLatch - no-op if already zero", async () => { await run(function* () { const { wait, countDown } = countdownLatch(1); countDown(); diff --git a/packages/effectstream-sdk/utils/src/concurrency/retry.test.ts b/packages/effectstream-sdk/utils/src/concurrency/retry.test.ts index 9532f1e10..d2825f2cd 100644 --- a/packages/effectstream-sdk/utils/src/concurrency/retry.test.ts +++ b/packages/effectstream-sdk/utils/src/concurrency/retry.test.ts @@ -1,8 +1,9 @@ import { assertEquals, assertRejects } from "jsr:@std/assert"; +import { test } from "@effectstream/utils/runtime"; import { retry, tryYield } from "./retry.ts"; import { run, sleep } from "effection"; -Deno.test("retry - succeeds immediately", async () => { +test("retry - succeeds immediately", async () => { await run(function* () { const result = yield* retry( function* () { return "success"; }, @@ -12,7 +13,7 @@ Deno.test("retry - succeeds immediately", async () => { }); }); -Deno.test("retry - retries until success", async () => { +test("retry - retries until success", async () => { await run(function* () { let attempts = 0; const result = yield* retry( @@ -27,7 +28,7 @@ Deno.test("retry - retries until success", async () => { }); }); -Deno.test("retry - fails after max attempts", async () => { +test("retry - fails after max attempts", async () => { await assertRejects(async () => { await run(function* () { yield* retry( @@ -40,7 +41,7 @@ Deno.test("retry - fails after max attempts", async () => { }, Error, "Max attempts reached"); }); -Deno.test("tryYield - success case", async () => { +test("tryYield - success case", async () => { await run(function* () { const op = function* () { return 42; }; const result = yield* tryYield(op()); @@ -49,7 +50,7 @@ Deno.test("tryYield - success case", async () => { }); }); -Deno.test("tryYield - failure case", async () => { +test("tryYield - failure case", async () => { await run(function* () { const op = function* () { throw new Error("fail"); }; const result = yield* tryYield(op()); diff --git a/packages/effectstream-sdk/utils/src/config.ts b/packages/effectstream-sdk/utils/src/config.ts index fbc1bda1f..d19a393c0 100644 --- a/packages/effectstream-sdk/utils/src/config.ts +++ b/packages/effectstream-sdk/utils/src/config.ts @@ -10,17 +10,11 @@ // NOTE: To register a new config, we need to add it in the definitions, and then add the getter in the ENV class. // TODO: Is it possible to do this automatically, or just once? import { load } from "@std/dotenv"; +import { getEnv, setEnv } from "./runtime.ts"; -// Generate a random 16-character hex string for Midnight storage password -function generateRandomHex(length: number): string { - const bytes = new Uint8Array(length / 2); - crypto.getRandomValues(bytes); - return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join(""); -} - -const MIDNIGHT_STORAGE_PASSWORD_DEFAULT = generateRandomHex(16); +const MIDNIGHT_STORAGE_PASSWORD_DEFAULT = 'yourpasswordmypassword'; -const EFFECTSTREAM_ENV = Deno.env.get("EFFECTSTREAM_ENV"); +const EFFECTSTREAM_ENV = getEnv("EFFECTSTREAM_ENV"); if (EFFECTSTREAM_ENV) { await load({ envPath: `.env.${EFFECTSTREAM_ENV}`, // Uses .env_ @@ -281,7 +275,7 @@ export class ENV { return ENV.getConfig(definitions.ORCHESTRATOR_PORT); } static set ORCHESTRATOR_PORT(port: number) { - Deno.env.set(definitions.ORCHESTRATOR_PORT.key, String(port)); + setEnv(definitions.ORCHESTRATOR_PORT.key, String(port)); } static get TUI_LOG_URL(): string { return ENV.getConfig(definitions.TUI_LOG_URL); @@ -399,6 +393,6 @@ export class ENV { private static getEnv( key: string, ): string | undefined { - return Deno.env.get(key); + return getEnv(key); } } diff --git a/packages/effectstream-sdk/utils/src/mod.ts b/packages/effectstream-sdk/utils/src/mod.ts index 4d50bc15a..e80ce387f 100644 --- a/packages/effectstream-sdk/utils/src/mod.ts +++ b/packages/effectstream-sdk/utils/src/mod.ts @@ -5,3 +5,4 @@ export * from "./captcha.ts"; export * from "./binary-search.ts"; export * from "./concurrency/mod.ts"; export * from "./decorators/mod.ts"; +export * from "./runtime.ts"; diff --git a/packages/effectstream-sdk/utils/src/runtime-spawn.ts b/packages/effectstream-sdk/utils/src/runtime-spawn.ts new file mode 100644 index 000000000..c7c541e96 --- /dev/null +++ b/packages/effectstream-sdk/utils/src/runtime-spawn.ts @@ -0,0 +1,229 @@ +/** + * Cross-runtime process spawn abstraction. + * Use this instead of Deno.Command or node:child_process.spawn when you need + * code to run on Node, Bun, or Deno. + * + * This will be refactored to only use standard packages compatible with all runtimes. + */ + +/** Result of a spawned process. stdout/stderr are Web ReadableStreams so they can be pipeTo()'d. */ +export interface SpawnChild { + readonly pid: number; + /** Present when options.stdin === "piped". */ + readonly stdin?: WritableStream; + readonly stdout: ReadableStream; + readonly stderr: ReadableStream; + readonly status: Promise<{ success: boolean; code?: number; signal?: string }>; + kill(signal?: string): void; + ref(): void; +} + +export interface SpawnOptions { + args?: string[]; + cwd?: string; + env?: Record; + signal?: AbortSignal; + stdin?: "inherit" | "piped" | "null"; + stdout?: "inherit" | "piped" | "null"; + stderr?: "inherit" | "piped" | "null"; +} + +/** Output from a completed process (for spawnOutput). */ +export interface SpawnOutputResult { + stdout: Uint8Array; + stderr: Uint8Array; + success: boolean; + code?: number; + signal?: string; +} + +/** Empty stream used when stdio is "inherit" so callers can always use pipeTo. */ +function emptyReadableStream(): ReadableStream { + return new ReadableStream({ + start(controller) { + controller.close(); + }, + }); +} + +function spawnDeno(command: string, options: SpawnOptions = {}): SpawnChild { + const args = options.args ?? []; + const child = new (Deno as any).Command(command, { + args, + signal: options.signal, + cwd: options.cwd, + env: options.env, + stdin: options.stdin ?? "inherit", + stdout: options.stdout ?? "piped", + stderr: options.stderr ?? "piped", + }).spawn(); + + const stdout = + options.stdout === "piped" + ? (child.stdout as ReadableStream) + : emptyReadableStream(); + const stderr = + options.stderr === "piped" + ? (child.stderr as ReadableStream) + : emptyReadableStream(); + + const status = (child.status as Promise<{ success: boolean; code?: number; signal?: string }>).then( + (s) => ({ success: s.success, code: s.code, signal: s.signal }) + ); + + const stdin = + options.stdin === "piped" && (child as any).stdin + ? ((child as any).stdin as WritableStream) + : undefined; + + return { + get pid() { + return child.pid; + }, + stdin, + stdout, + stderr, + status, + kill(signal?: string) { + child.kill(signal ?? "SIGTERM"); + }, + ref() { + if (typeof child.ref === "function") child.ref(); + }, + }; +} + +function spawnNode(command: string, options: SpawnOptions = {}): SpawnChild { + const { spawn } = require("node:child_process"); + const { Readable } = require("node:stream"); + + const args = options.args ?? []; + const stdio: ("inherit" | "pipe" | "ignore")[] = [ + options.stdin === "piped" ? "pipe" : options.stdin === "null" ? "ignore" : "inherit", + options.stdout === "piped" ? "pipe" : options.stdout === "null" ? "ignore" : "inherit", + options.stderr === "piped" ? "pipe" : options.stderr === "null" ? "ignore" : "inherit", + ]; + + const cp = spawn(command, args, { + cwd: options.cwd, + env: { ...options.env, FORCE_COLOR: "true" }, + stdio, + signal: options.signal, + }); + + const stdout: ReadableStream = + options.stdout === "piped" && cp.stdout + ? (Readable.toWeb(cp.stdout) as ReadableStream) + : emptyReadableStream(); + const stderr: ReadableStream = + options.stderr === "piped" && cp.stderr + ? (Readable.toWeb(cp.stderr) as ReadableStream) + : emptyReadableStream(); + + const status = new Promise<{ success: boolean; code?: number; signal?: string }>( + (resolve) => { + cp.on("close", (code: number | null, signal: string | null) => { + resolve({ + success: code === 0, + code: code ?? undefined, + signal: signal ?? undefined, + }); + }); + } + ); + + let stdinStream: WritableStream | undefined; + if (options.stdin === "piped" && cp.stdin) { + stdinStream = require("node:stream").Writable.toWeb(cp.stdin) as WritableStream; + } + + return { + get pid() { + return cp.pid ?? 0; + }, + stdin: stdinStream, + stdout, + stderr, + status, + kill(signal?: string) { + cp.kill(signal ?? "SIGTERM"); + }, + ref() { + if (typeof cp.ref === "function") cp.ref(); + }, + }; +} + +/** + * Spawn a child process. Returns a handle with Web ReadableStreams for stdout/stderr + * so they can be used with pipeTo() in any runtime. + */ +export function spawn(command: string, options: SpawnOptions = {}): SpawnChild { + if (typeof Deno !== "undefined" && (Deno as any).Command) { + return spawnDeno(command, options); + } + if (typeof process !== "undefined" && process.versions?.node) { + return spawnNode(command, options); + } + throw new Error("No process spawn implementation (Deno.Command or node:child_process) available"); +} + +/** + * Run a command and wait for it to complete, returning collected stdout/stderr. + * Use for short-lived commands (e.g. tmux install, kill-server). + */ +export async function spawnOutput( + command: string, + options: SpawnOptions & { stdinInput?: Uint8Array } = {} +): Promise { + const { stdinInput, ...spawnOpts } = options; + const useStdin = stdinInput != null; + const child = spawn(command, { + ...spawnOpts, + stdin: useStdin ? "piped" : spawnOpts.stdin, + stdout: "piped", + stderr: "piped", + }); + + if (useStdin && child.stdin) { + const writer = child.stdin.getWriter(); + await writer.write(stdinInput); + await writer.close(); + } + + const [stdout, stderr, status] = await Promise.all([ + streamToUint8Array(child.stdout), + streamToUint8Array(child.stderr), + child.status, + ]); + + return { + stdout, + stderr, + success: status.success, + code: status.code, + signal: status.signal, + }; +} + +async function streamToUint8Array(stream: ReadableStream): Promise { + const chunks: Uint8Array[] = []; + const reader = stream.getReader(); + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + if (value) chunks.push(value); + } + } finally { + reader.releaseLock(); + } + const total = chunks.reduce((n, c) => n + c.length, 0); + const out = new Uint8Array(total); + let offset = 0; + for (const c of chunks) { + out.set(c, offset); + offset += c.length; + } + return out; +} diff --git a/packages/effectstream-sdk/utils/src/runtime.ts b/packages/effectstream-sdk/utils/src/runtime.ts new file mode 100644 index 000000000..061a15c4f --- /dev/null +++ b/packages/effectstream-sdk/utils/src/runtime.ts @@ -0,0 +1,137 @@ +declare const process: { + env: Record; + argv: string[]; + exit(code?: number): never; + cwd(): string; + stdout: { columns?: number; rows?: number }; + on(sig: string, fn: () => void): void; + off(sig: string, fn: () => void): void; + kill?(pid: number, sig?: string): void; +}; + +type RuntimeEnvironment = 'backend' | 'frontend' | 'unknown'; +type Runtime = 'node' | 'deno' | 'bun' | 'browser' | 'unknown'; + +export function getRuntime(): {runtime: Runtime, environment: RuntimeEnvironment} { + // Check for Deno + // @ts-ignore: Deno is added to global scope in Deno environments + if (typeof Deno !== 'undefined') { + return { runtime: 'deno', environment: 'backend' }; + } + + // Check for Bun + // @ts-ignore: Bun is added to global scope in Bun environments + if (typeof Bun !== 'undefined') { + return { runtime: 'bun', environment: 'backend' }; + } + + // Check for Node.js + // Node environments have a `process` object with versions + if ( + typeof process !== 'undefined' && + (process as any).versions && + (process as any).versions.node + ) { + return { runtime: 'node', environment: 'backend' }; + } + + // Check for Browser + // Browsers typically have `window` and `document` + if ( + typeof window !== 'undefined' && + // @ts-ignore: document is added to global scope in browser environments + typeof document !== 'undefined' + ) { + return { runtime: 'browser', environment: 'frontend' }; + } + + return { runtime: 'unknown', environment: 'unknown' }; +} + +/** Get environment variable. */ +export function getEnv(key: string): string | undefined { + if (typeof process !== "undefined" && process.env) return process.env[key]; + return undefined; +} + +/** Set environment variable. */ +export function setEnv(key: string, value: string): void { + if (typeof process !== "undefined" && process.env) { + process.env[key] = value; + } +} + +/** CLI arguments (argv without script path). */ +export function args(): string[] { + return process.argv.slice(2); +} + +/** Exit process with code. */ +export function exit(code: number | undefined): never { + if (code === undefined && (process as any).exitCode !== undefined) { + process.exit((process as any).exitCode); + } else { + process.exit(code); + } +} + +/** Current working directory. */ +export function cwd(): string { + if (typeof process !== "undefined" && process.cwd) return process.cwd(); + return "."; +} + +/** Terminal size when available. */ +export function consoleSize(): { columns: number; rows: number } { + if (typeof process !== "undefined" && process.stdout?.columns != null) { + return { columns: process.stdout.columns ?? 80, rows: (process.stdout as any).rows ?? 24 }; + } + return { columns: 80, rows: 24 }; +} + +/** Set process exit code (for graceful shutdown). */ +export function setExitCode(code: number): void { + (process as any).exitCode = code; +} + +/** + * Register a test. + * Use: import { test } from "@effectstream/utils/runtime"; + */ +export function test( + name: string, + fn: () => void | Promise, +): void { + if (typeof process !== "undefined") { + try { + // dynamic import for ESM; require for CJS + const mod = typeof require !== "undefined" + ? require("node:test") + : null; + if (mod?.test) mod.test(name, fn); + return; + } catch { + // node:test not available + } + } + throw new Error("No test runner available"); +} + +/** Check if error is "file/dir not found". */ +export function isNotFoundError(err: unknown): boolean { + if (err && typeof err === "object") { + const e = err as { code?: string; name?: string }; + if (e.code === "ENOENT") return true; + if (e.name === "NotFound" || (e as any).constructor?.name === "NotFound") return true; + } + return false; +} + +/** Cross-runtime process spawn. Re-exported from runtime-spawn. */ +export { + spawn, + spawnOutput, + type SpawnChild, + type SpawnOptions, + type SpawnOutputResult, +} from "./runtime-spawn.ts"; diff --git a/packages/effectstream-sdk/wallets/scripts/build_npm.ts b/packages/effectstream-sdk/wallets/scripts/build_npm.ts index f31a014e2..97ad86f74 100644 --- a/packages/effectstream-sdk/wallets/scripts/build_npm.ts +++ b/packages/effectstream-sdk/wallets/scripts/build_npm.ts @@ -1,6 +1,8 @@ import { build, emptyDir } from "@deno/dnt"; +import { copyFileSync } from "node:fs"; +import { args } from "@effectstream/utils/runtime"; -const version = Deno.args[0]; +const version = args()[0]; if (!version) { throw new Error("Version is required"); } @@ -29,8 +31,8 @@ await build({ }, postBuild() { // steps to run after building and before running the tests - // Deno.copyFileSync("LICENSE", "npm/LICENSE"); - Deno.copyFileSync("README.md", "npm/README.md"); + // copyFileSync("LICENSE", "npm/LICENSE"); + copyFileSync("README.md", "npm/README.md"); }, configFile: import.meta.resolve("../deno.json"), compilerOptions: { diff --git a/packages/effectstream-sdk/wallets/src/utils.test.ts b/packages/effectstream-sdk/wallets/src/utils.test.ts index 15ef9de1c..7d974b778 100644 --- a/packages/effectstream-sdk/wallets/src/utils.test.ts +++ b/packages/effectstream-sdk/wallets/src/utils.test.ts @@ -4,6 +4,7 @@ import { } from "./utils.ts"; import type { IInjectedConnector, IProvider } from "./IProvider.ts"; import { AddressType } from "@effectstream/utils"; +import { test } from "@effectstream/utils/runtime"; const MOCK_ADDRESS = "0xmock"; const MOCK_METADATA = { name: "MockWallet", displayName: "Mock Wallet", icon: "", version: "1.0" }; @@ -31,20 +32,20 @@ const createMockConnector = (shouldFail = false): IInjectedConnector => }, }); -Deno.test("connectInjectedWallet - simple login success", async () => { +test("connectInjectedWallet - simple login success", async () => { const connector = createMockConnector(); const provider = await connectInjectedWallet("Test", undefined, connector); assertEquals(provider.getAddress().address, MOCK_ADDRESS); }); -Deno.test("connectInjectedWallet - named login success", async () => { +test("connectInjectedWallet - named login success", async () => { const connector = createMockConnector(); const preference = { name: "MockWallet" }; const provider = await connectInjectedWallet("Test", preference, connector); assertEquals(provider.getAddress().address, MOCK_ADDRESS); }); -Deno.test("connectInjectedWallet - external connection success", async () => { +test("connectInjectedWallet - external connection success", async () => { const connector = createMockConnector(); const preference = { connection: { @@ -56,7 +57,7 @@ Deno.test("connectInjectedWallet - external connection success", async () => { assertEquals(provider.getAddress().address, MOCK_ADDRESS); }); -Deno.test("connectInjectedWallet - handles failure", async () => { +test("connectInjectedWallet - handles failure", async () => { const connector = createMockConnector(true); await assertRejects( () => connectInjectedWallet("Test", undefined, connector), diff --git a/packages/node-sdk/db/scripts/apply-migrations.ts b/packages/node-sdk/db/scripts/apply-migrations.ts index b0c820e88..b244d7c33 100644 --- a/packages/node-sdk/db/scripts/apply-migrations.ts +++ b/packages/node-sdk/db/scripts/apply-migrations.ts @@ -45,5 +45,5 @@ if (import.meta.main) { const db = await getConnection(); await standAloneApplyInitialMigrations(db, 0); console.log("โœ… System migrations applied"); - Deno.exit(0); + process.exit(0); } diff --git a/packages/node-sdk/db/scripts/pgtyped-internal.ts b/packages/node-sdk/db/scripts/pgtyped-internal.ts index 3573e8b76..7120948ee 100644 --- a/packages/node-sdk/db/scripts/pgtyped-internal.ts +++ b/packages/node-sdk/db/scripts/pgtyped-internal.ts @@ -1,30 +1,32 @@ #!/usr/bin/env -S deno run -A +import { spawn } from "node:child_process"; import { waitForDb } from "./wait-for-db.ts"; import { dirname, join } from "jsr:@std/path@1.1.3"; +import { fileURLToPath } from "node:url"; async function runPgtyped() { try { console.log("๐Ÿ”„ Running pgtyped..."); - const __dirname = dirname(import.meta.url.replace("file://", "")); + const __dirname = dirname(import.meta.url?.startsWith("file:") ? fileURLToPath(import.meta.url) : import.meta.url.replace("file://", "")); const configPath = join(__dirname, "../pgtypedconfig.json"); - const command = new Deno.Command("npx", { - args: ["pgtyped", "-c", configPath], - stdout: "inherit", - stderr: "inherit", + const child = spawn("npx", ["pgtyped", "-c", configPath], { + stdio: "inherit", + shell: true, }); - const child = command.spawn(); - const status = await child.status; + const code = await new Promise((resolve) => { + child.on("close", (code) => resolve(code)); + }); - if (status.success) { + if (code === 0) { console.log("โœ… pgtyped completed successfully"); } else { console.error("โŒ pgtyped failed"); - Deno.exit(status.code); + process.exit(code ?? 1); } } catch (error) { console.error("โŒ Error running pgtyped:", error); - Deno.exit(1); + process.exit(1); } } diff --git a/packages/node-sdk/db/scripts/pgtyped-update.ts b/packages/node-sdk/db/scripts/pgtyped-update.ts index 87634cd20..da3bef842 100644 --- a/packages/node-sdk/db/scripts/pgtyped-update.ts +++ b/packages/node-sdk/db/scripts/pgtyped-update.ts @@ -3,9 +3,11 @@ * Runs database startup and pgtyped generation concurrently */ +import { spawn } from "node:child_process"; import { dirname, join } from "jsr:@std/path@1.1.3"; +import { fileURLToPath } from "node:url"; -const __dirname = dirname(import.meta.url.replace("file://", "")); +const __dirname = dirname(import.meta.url?.startsWith("file:") ? fileURLToPath(import.meta.url) : import.meta.url.replace("file://", "")); interface ProcessInfo { name: string; @@ -29,70 +31,43 @@ async function runProcess(processInfo: ProcessInfo): Promise { console.log(`${prefix}Starting...`); - const command = new Deno.Command(processInfo.command, { - args: processInfo.args, - stdout: "piped", - stderr: "piped", + const child = spawn(processInfo.command, processInfo.args, { + stdio: ["inherit", "pipe", "pipe"], + shell: processInfo.command === "npx", }); - const process = command.spawn(); - - // Handle stdout - const stdoutReader = process.stdout.getReader(); - const stderrReader = process.stderr.getReader(); + const stdoutPromise = new Promise((resolve) => { + child.stdout?.on("data", (chunk: Buffer | string) => { + const text = chunk.toString(); + text.split("\n").forEach((line) => { + if (line.trim()) console.log(`${prefix}${line}`); + }); + }); + child.stdout?.on("end", resolve); + if (!child.stdout) resolve(); + }); - // Stream stdout - const stdoutPromise = (async () => { - const decoder = new TextDecoder(); - try { - while (true) { - const { done, value } = await stdoutReader.read(); - if (done) break; - const text = decoder.decode(value); - // Print each line with prefix - text.split("\n").forEach((line) => { - if (line.trim()) { - console.log(`${prefix}${line}`); - } - }); - } - } finally { - stdoutReader.releaseLock(); - } - })(); + const stderrPromise = new Promise((resolve) => { + child.stderr?.on("data", (chunk: Buffer | string) => { + const text = chunk.toString(); + text.split("\n").forEach((line) => { + if (line.trim()) console.error(`${prefix}${line}`); + }); + }); + child.stderr?.on("end", resolve); + if (!child.stderr) resolve(); + }); - // Stream stderr - const stderrPromise = (async () => { - const decoder = new TextDecoder(); - try { - while (true) { - const { done, value } = await stderrReader.read(); - if (done) break; - const text = decoder.decode(value); - // Print each line with prefix - text.split("\n").forEach((line) => { - if (line.trim()) { - console.error(`${prefix}${line}`); - } - }); - } - } finally { - stderrReader.releaseLock(); - } - })(); + const exitCode = new Promise((resolve) => { + child.on("close", (code) => resolve(code)); + }); if (processInfo.wait) { - // Wait for process to complete - const [status] = await Promise.all([ - process.status, - stdoutPromise, - stderrPromise, - ]); - - if (status.success) { + const [code] = await Promise.all([exitCode, stdoutPromise, stderrPromise]); + if (code === 0) { console.log(`${prefix}โœ… Completed successfully`); } else { - console.error(`${prefix}โŒ Failed with exit code ${status.code}`); + console.error(`${prefix}โŒ Failed with exit code ${code}`); throw new Error(`Process ${processInfo.name} failed`); } } @@ -135,7 +110,7 @@ async function main() { console.log("\nโœ… All processes completed successfully"); } catch (error) { console.error("\nโŒ One or more processes failed:", error); - Deno.exit(1); + process.exit(1); } } diff --git a/packages/node-sdk/db/scripts/start-pglite.ts b/packages/node-sdk/db/scripts/start-pglite.ts index 7b30fd8a8..2a2f088b2 100644 --- a/packages/node-sdk/db/scripts/start-pglite.ts +++ b/packages/node-sdk/db/scripts/start-pglite.ts @@ -2,31 +2,35 @@ import { type DebugLevel, PGlite } from "@electric-sql/pglite"; // TODO This is not working, so we load the pg_ivm extension from the node_modules folder // import { pg_ivm } from "@electric-sql/pglite/pg_ivm"; import net from "node:net"; +import { statSync } from "node:fs"; import { fromNodeSocket } from "pg-gateway/node"; import { ENV } from "@effectstream/utils/node-env"; +import { args, cwd } from "@effectstream/utils/runtime"; // TODO PORT be a ENV variable // Get port from arguments. const portArgName = "--port"; -const portArgIndex = Deno.args.indexOf(portArgName); -const portValue = portArgIndex !== -1 ? Deno.args[portArgIndex + 1] : "5432"; +const argv = args(); +const portArgIndex = argv.indexOf(portArgName); +const portValue = portArgIndex !== -1 ? argv[portArgIndex + 1] : "5432"; const port = parseInt(portValue); if (isNaN(port)) { throw new Error(`Port argument ${portArgName} is not a number`); } // TODO: find nearest node_modules folder, as import { pg_ivm } is not working -let nodeModulesPath = Deno.cwd(); +let nodeModulesPath = cwd(); while (true) { try { - !Deno.statSync(nodeModulesPath + "/node_modules").isDirectory; - break; - } catch (e) { - if (!nodeModulesPath || nodeModulesPath === "/") { - throw new Error("Node modules not found"); - } - nodeModulesPath = nodeModulesPath.split("/").slice(0, -1).join("/"); + const st = statSync(nodeModulesPath + "/node_modules"); + if (st.isDirectory()) break; + } catch (_e) { + // not found or not dir } + if (!nodeModulesPath || nodeModulesPath === "/") { + throw new Error("Node modules not found"); + } + nodeModulesPath = nodeModulesPath.split("/").slice(0, -1).join("/"); } const db = new PGlite( diff --git a/packages/node-sdk/db/scripts/wait-for-db.ts b/packages/node-sdk/db/scripts/wait-for-db.ts index 1d1dbd0f6..5bec4ff31 100644 --- a/packages/node-sdk/db/scripts/wait-for-db.ts +++ b/packages/node-sdk/db/scripts/wait-for-db.ts @@ -1,8 +1,12 @@ #!/usr/bin/env -S deno run -A +import { spawn } from "node:child_process"; +import { args, exit } from "@effectstream/utils/runtime"; + // Get port from arguments. const portArgName = "--port"; -const portArgIndex = Deno.args.indexOf(portArgName); -const portValue = portArgIndex !== -1 ? Deno.args[portArgIndex + 1] : "5432"; +const argv = args(); +const portArgIndex = argv.indexOf(portArgName); +const portValue = portArgIndex !== -1 ? argv[portArgIndex + 1] : "5432"; const port = parseInt(portValue); if (isNaN(port)) { throw new Error(`Port argument ${portArgName} is not a number`); @@ -11,24 +15,24 @@ if (isNaN(port)) { async function waitForDb() { try { console.log("waiting for db on port", port); - const command = new Deno.Command("deno", { - args: ["-A", "npm:wait-on", `tcp:${port}`], - stdout: "inherit", - stderr: "inherit", + const child = spawn("npx", ["wait-on", `tcp:${port}`], { + stdio: "inherit", + shell: true, }); - const child = command.spawn(); - const status = await child.status; + const code = await new Promise((resolve) => { + child.on("close", (code, _sig) => resolve(code)); + }); - if (status.success) { + if (code === 0) { console.log("โœ… Database is ready on port 5432"); } else { console.error("โŒ Failed to connect to database on port 5432"); - Deno.exit(status.code); + exit(code ?? 1); } } catch (error) { console.error("โŒ Error waiting for database:", error); - Deno.exit(1); + exit(1); } } diff --git a/packages/node-sdk/db/src/pg-connection.test.ts b/packages/node-sdk/db/src/pg-connection.test.ts index 1a44a2c7f..f7dcf70ce 100644 --- a/packages/node-sdk/db/src/pg-connection.test.ts +++ b/packages/node-sdk/db/src/pg-connection.test.ts @@ -5,11 +5,12 @@ import { waitUntilFree, } from "./pg-connection.ts"; import { run, sleep, spawn } from "effection"; +import { test, setEnv } from "@effectstream/utils/runtime"; // Force PGLITE env var to true for testing mutex logic -Deno.env.set("PGLITE", "true"); +setEnv("PGLITE", "true"); -Deno.test("DB Mutex - acquires and releases lock", async () => { +test("DB Mutex - acquires and releases lock", async () => { await run(function* () { const lockName = "test-lock"; @@ -25,7 +26,7 @@ Deno.test("DB Mutex - acquires and releases lock", async () => { }); }); -Deno.test("DB Mutex - queues requests", async () => { +test("DB Mutex - queues requests", async () => { await run(function* () { const lock1 = "lock-1"; const lock2 = "lock-2"; @@ -59,7 +60,7 @@ Deno.test("DB Mutex - queues requests", async () => { }); }); -Deno.test("DB Mutex - respects priority", async () => { +test("DB Mutex - respects priority", async () => { await run(function* () { const mainLock = "main"; const lowPrio = "low"; diff --git a/packages/node-sdk/runtime/src/main.ts b/packages/node-sdk/runtime/src/main.ts index 76decfd36..e57492690 100644 --- a/packages/node-sdk/runtime/src/main.ts +++ b/packages/node-sdk/runtime/src/main.ts @@ -60,7 +60,7 @@ export function* start(config: StartConfig): Operation { ComponentNames.EFFECTSTREAM_RUNTIME, [], SeverityNumber.INFO, - (log) => log("start sync"), + (log) => log("start sync", syncProtocols.map(p => p.name)), ); for (const syncProtocol of syncProtocols) { yield* startSync(syncProtocol); diff --git a/packages/node-sdk/sm/primitives/src/evm-erc20/erc20-primitive.test.ts b/packages/node-sdk/sm/primitives/src/evm-erc20/erc20-primitive.test.ts index dbe5864a2..0fc617801 100644 --- a/packages/node-sdk/sm/primitives/src/evm-erc20/erc20-primitive.test.ts +++ b/packages/node-sdk/sm/primitives/src/evm-erc20/erc20-primitive.test.ts @@ -1,5 +1,6 @@ import { assertEquals, assertThrows } from "jsr:@std/assert"; import type { EvmAddress } from "@effectstream/utils"; +import { test } from "@effectstream/utils/runtime"; import { run } from "effection"; import type { ConfigSyncProtocolType, FlattenSyncProtocolIOFor } from "@effectstream/config"; import { Erc20Primitive } from "./../mod.ts"; @@ -18,7 +19,7 @@ function cleanup() { PaimaPrimitiveRegistry.primitives = {}; } -Deno.test("Erc20Primitive - initializes correctly", () => { +test("Erc20Primitive - initializes correctly", () => { cleanup(); const primitive = new Erc20Primitive({ instanceName: "test-token", @@ -38,7 +39,7 @@ Deno.test("Erc20Primitive - initializes correctly", () => { assertEquals(config.contractAddress, MOCK_CONTRACT_ADDRESS); }); -Deno.test("Erc20Primitive - throws on invalid address", () => { +test("Erc20Primitive - throws on invalid address", () => { cleanup(); assertThrows(() => { new Erc20Primitive({ @@ -50,7 +51,7 @@ Deno.test("Erc20Primitive - throws on invalid address", () => { }); }); -Deno.test("Erc20Primitive - getPayload generates correct state update", async () => { +test("Erc20Primitive - getPayload generates correct state update", async () => { cleanup(); const primitive = new Erc20Primitive({ instanceName: "test-token", @@ -91,7 +92,7 @@ Deno.test("Erc20Primitive - getPayload generates correct state update", async () }); }); -Deno.test("Erc20Primitive - getPayload skips state machine payload if no prefix", async () => { +test("Erc20Primitive - getPayload skips state machine payload if no prefix", async () => { cleanup(); const primitive = new Erc20Primitive({ instanceName: "test-token",