Skip to content

Commit

Permalink
Add Aptos CLI (#246)
Browse files Browse the repository at this point in the history
* add cli module

* add aptoscli npm package and use it in integration tests

* disable localnet process for sdk tests on ci

* clean up code and update readme

* fix lint

* address comments
  • Loading branch information
0xmaayan authored Jan 3, 2024
1 parent 27302a7 commit 2185a7f
Show file tree
Hide file tree
Showing 10 changed files with 144 additions and 17 deletions.
16 changes: 0 additions & 16 deletions .github/actions/run-tests/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,6 @@ runs:
- run: pnpm install --frozen-lockfile
shell: bash

# Install the CLI.
- run: pnpm install -g @aptos-labs/aptos-cli
shell: bash

# Run a local testnet in the background.
- run: aptos node run-local-testnet --force-restart --assume-yes --with-indexer-api --log-to-stdout >& ${{ runner.temp }}/local-testnet-logs.txt &
shell: bash

# Wait for the local testnet to be ready by hitting the readiness endpoint.
# We give it a while because the CLI will have to download some images before
# actually running the local testnet, which can take a while.
- run: pnpm install -g wait-on
shell: bash
- run: wait-on --verbose --interval 1500 --timeout 120000 --httpTimeout 120000 http-get://127.0.0.1:8070
shell: bash

# Run the TS SDK tests.
- uses: nick-fields/retry@7f8f3d9f0f62fe5925341be21c2e8314fd4f7c7c # pin@v2
name: sdk-pnpm-test
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ All notable changes to the Aptos TypeScript SDK will be captured in this file. T
- [`Breaking`] Capitalize `TransactionPayloadMultiSig` type
- Add support to Array value in digital asset property map
- [`Breaking`] Change `maxGasAmount, gasUnitPrice and expireTimestamp` properties in `InputGenerateTransactionOptions` type to `number` type
- Add `@aptos-labs/aptos-cli` npm package as a dev dependency
- Implement a `LocalNode` module to run a local testnet with in the SDK environment
- Use `LocalNode` module to spin up a local testnet pre running SDK tests

# 1.2.0 (2023-12-14)

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,10 @@ const pendingTransaction = await aptos.signAndSubmitTransaction({ signer: alice,
To run the SDK tests, simply run from the root of this repository:
> Note: make sure aptos local node is up and running. Take a look at the [local development network guide](https://aptos.dev/guides/local-development-network/) for more details.
> Note: for a better experience, make sure there is no aptos local node process up and running (can check if there is a process running on port 8080).
```bash
pnpm i
pnpm test
```
Expand Down
2 changes: 2 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ module.exports = {
},
// To help avoid exhausting all the available fds.
maxWorkers: 4,
globalSetup: "./tests/preTest.js",
globalTeardown: "./tests/postTest.js",
};
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"tweetnacl": "^1.0.3"
},
"devDependencies": {
"@aptos-labs/aptos-cli": "^0.1.2",
"@babel/traverse": "^7.23.6",
"@graphql-codegen/cli": "^5.0.0",
"@graphql-codegen/import-types-preset": "^3.0.0",
Expand All @@ -74,6 +75,7 @@
"graphql-request": "^6.1.0",
"jest": "^29.7.0",
"prettier": "^3.1.1",
"tree-kill": "^1.2.2",
"ts-jest": "^29.1.1",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
Expand Down
11 changes: 11 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./localNode";
105 changes: 105 additions & 0 deletions src/cli/localNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { ChildProcessWithoutNullStreams, spawn } from "child_process";
import kill from "tree-kill";
import { sleep } from "../utils/helpers";

export class LocalNode {
readonly MAXIMUM_WAIT_TIME_SEC = 30;

readonly READINESS_ENDPOINT = "http://127.0.0.1:8070/";

process: ChildProcessWithoutNullStreams | null = null;

/**
* kills all the descendent processes
* of the node process, including the node process itself
*/
stop() {
if (!this.process?.pid) return;
kill(this.process.pid);
}

/**
* Runs a local testnet and waits for process to be up.
*
* If local node process is already up it returns and does
* not start the process
*/
async run() {
const nodeIsUp = await this.checkIfProcessIsUp();
if (nodeIsUp) {
return;
}
this.start();
await this.waitUntilProcessIsUp();
}

/**
* Starts the local testnet by running the aptos node run-local-testnet command
*/
start() {
const cliCommand = "npx";
const cliArgs = ["aptos", "node", "run-local-testnet", "--force-restart", "--assume-yes", "--with-indexer-api"];

const childProcess = spawn(cliCommand, cliArgs);
this.process = childProcess;

childProcess.stderr?.on("data", (data: any) => {
const str = data.toString();
// Print local node output log
// eslint-disable-next-line no-console
console.log(str);
});

childProcess.stdout?.on("data", (data: any) => {
const str = data.toString();
// Print local node output log
// eslint-disable-next-line no-console
console.log(str);
});
}

/**
* Waits for the local testnet process to be up
*
* @returns Promise<boolean>
*/
async waitUntilProcessIsUp(): Promise<boolean> {
let operational = await this.checkIfProcessIsUp();
const start = Date.now() / 1000;
let last = start;

while (!operational && start + this.MAXIMUM_WAIT_TIME_SEC > last) {
// eslint-disable-next-line no-await-in-loop
await sleep(1000);
// eslint-disable-next-line no-await-in-loop
operational = await this.checkIfProcessIsUp();
last = Date.now() / 1000;
}

// If we are here it means something blocks the process to start.
// Might worth checking if another process is running on port 8080
if (!operational) {
throw new Error("Process failed to start");
}

return true;
}

/**
* Checks if the local testnet is up
*
* @returns Promise<boolean>
*/
async checkIfProcessIsUp(): Promise<boolean> {
try {
// Query readiness endpoint
const data = await fetch(this.READINESS_ENDPOINT);
if (data.status === 200) {
return true;
}
return false;
} catch (err: any) {
return false;
}
}
}
11 changes: 11 additions & 0 deletions tests/postTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = async function () {
// Check if the current local node process is
// from within the sdk node environment
if (globalThis.__LOCAL_NODE__.process) {
const aptosNode = globalThis.__LOCAL_NODE__;
// Local node runs multiple procceses, to avoid asynchronous operations
// that weren't stopped in our tests, we kill all the descendent processes
// of the node process, including the node process itself
aptosNode.stop();
}
};
7 changes: 7 additions & 0 deletions tests/preTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const { LocalNode } = require("../src/cli");

module.exports = async function setup() {
const localNode = new LocalNode();
globalThis.__LOCAL_NODE__ = localNode;
await localNode.run();
};

0 comments on commit 2185a7f

Please sign in to comment.