Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Aptos CLI #246

Merged
merged 6 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can probably return void promise then

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();
};
Loading