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

Feature run scripts #148

Closed
wants to merge 5 commits into from
Closed
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
2 changes: 1 addition & 1 deletion packages/cli/src/commands/contract/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ export class CompileContract extends Command {

const buildData = (await spinner.runCommand(async () => {
return storeArtifacts(artifactsPath, contractInfo.name);
}, "Moving artifacts")) as BuildData;
}, "Storing artifacts")) as BuildData;

contractInfo.build = buildData;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/contract/deploy.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Args, Command, Flags } from "@oclif/core";
import path = require("node:path");
import { readJSON, readFile, writeJSON } from "fs-extra";
import { readJSON, writeJSON } from "fs-extra";
import { cryptoWaitReady } from "@polkadot/util-crypto";
import {
ensureSwankyProject,
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/contract/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import path = require("node:path");
import { ensureSwankyProject, getSwankyConfig } from "@astar-network/swanky-core";
import globby from "globby";
import Mocha from "mocha";
import { ensureDir, readdirSync } from "fs-extra";
import { ensureDir } from "fs-extra";
import * as shell from "shelljs";

declare global {
Expand Down
49 changes: 49 additions & 0 deletions packages/cli/src/commands/script/run.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { BaseCommand } from "../../lib/baseCommand";
import { Args } from "@oclif/core";
import { ensureSwankyProject } from "@astar-network/swanky-core";
import { existsSync } from "fs-extra";
import { fork } from "child_process";
import path = require("node:path");

export class RunCommand extends BaseCommand<typeof RunCommand> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

having both script and run looks redundant.
Why not use just run, so it's more in line with convention in f.ex. npm (swanky run deploy)?

static description = "Run a user-defined scripts";

static args = {
scriptName: Args.string({
name: "scriptName",
required: true,
description: "Name of the script to run",
}),
};

async run(): Promise<void> {
await ensureSwankyProject();

const { args } = await this.parse(RunCommand);

let scriptName = args.scriptName;
if (!scriptName.endsWith(".ts")) {
scriptName += ".ts";
}

const scriptPath = path.resolve("scripts", scriptName);
if (!existsSync(scriptPath)) {
throw new Error(`Script ${args.scriptName} does not exist`)
}

await new Promise((resolve, reject) => {
const childProcess = fork(scriptPath, [], {
stdio: "inherit",
execArgv: ["--require", "ts-node/register/transpile-only"],
env: { ...process.env },
});
Comment on lines +34 to +39
Copy link
Collaborator

Choose a reason for hiding this comment

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

Have you considered dynamically loading and executing the script instead of running it in a separate process?
That way we could provide for example a runner class, and have it more standardised.
This has an example of it:
https://betterprogramming.pub/how-to-run-typescript-in-javascript-1545e8a36518


childProcess.once("close", (status) => {
this.log(`Script ${scriptPath} exited with status code ${status ?? "null"}`);

resolve(status as number);
});
childProcess.once("error", reject);
})
}
}
28 changes: 7 additions & 21 deletions packages/core/src/lib/command-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import path = require("node:path");
import { DEFAULT_NETWORK_URL, STORED_ARTIFACTS_PATH } from "./consts.js";
import { BuildData, SwankyConfig } from "../types";
import { Abi } from "@polkadot/api-contract";
import { TEMP_ARTIFACTS_PATH, TEMP_TYPED_CONTRACT_PATH } from "./consts";
import { TEMP_ARTIFACTS_PATH } from "./consts";

export async function commandStdoutOrNull(command: string): Promise<string | null> {
try {
Expand Down Expand Up @@ -69,21 +69,6 @@ export async function storeArtifacts(
`${buildData.artifactsPath}/${contractName}.json`
),
]);
// move both to test/contract_name/artifacts
const testArtifacts = path.resolve("test", contractName, "artifacts");
await fs.ensureDir(testArtifacts);
await Promise.all([
fs.move(
path.resolve(artifactsPath, `${contractName}.contract`),
`${testArtifacts}/${contractName}.contract`,
{ overwrite: true }
),
fs.move(
path.resolve(artifactsPath, `${contractName}.json`),
`${testArtifacts}/${contractName}.json`,
{ overwrite: true }
),
]);
} catch (e) {
console.error(e);
}
Expand Down Expand Up @@ -134,7 +119,7 @@ export async function printContractInfo(metadataPath: string) {
}
}

export async function generateTypes(inputAbsPath: string, contractName: string, outputAbsPath: string) {
export async function generateTypes(inputPath: string, contractName: string, outputPath: string) {
await fs.ensureDir(TEMP_ARTIFACTS_PATH);

// Getting error if typechain-polkadot takes folder with unnecessary files/folders as inputs.
Expand All @@ -150,15 +135,16 @@ export async function generateTypes(inputAbsPath: string, contractName: string,
// Cannot generate typedContract directly to `outputAbsPath`
// because relative path of typechain-polkadot input and output folder does matter for later use.
await fs.copyFile(
path.resolve(inputAbsPath, `${contractName}.contract`),
path.resolve(inputPath, `${contractName}.contract`),
path.resolve(TEMP_ARTIFACTS_PATH, `${contractName}.contract`),
),
await fs.copyFile(
path.resolve(inputAbsPath, `${contractName}.json`),
path.resolve(inputPath, `${contractName}.json`),
path.resolve(TEMP_ARTIFACTS_PATH, `${contractName}.json`),
)

await execa.command(`npx typechain-polkadot --in ${TEMP_ARTIFACTS_PATH} --out ${TEMP_TYPED_CONTRACT_PATH}`);
const outputRelativePath = path.relative(path.resolve(), path.resolve(outputPath));
await execa.command(`npx typechain-polkadot --in ${TEMP_ARTIFACTS_PATH} --out ${outputRelativePath}`);

await fs.move(path.resolve(TEMP_TYPED_CONTRACT_PATH), outputAbsPath)
await fs.remove(TEMP_ARTIFACTS_PATH);
}
7 changes: 1 addition & 6 deletions packages/core/src/lib/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,4 @@ export const DEFAULT_SHIDEN_NETWORK_URL = "wss://rpc.shiden.astar.network";
export const DEFAULT_SHIBUYA_NETWORK_URL = "wss://rpc.shibuya.astar.network";

export const STORED_ARTIFACTS_PATH = "./artifacts";

// typechain-polkadot's output files are tightly coupled with input folder path.
// ./artifacts folder is used not only for storing historical artifacts, but also as typechain-polkadot's input folder.
// So, name duplication with `STORED_ARTIFACTS_PATH` is expected at least for now.
export const TEMP_ARTIFACTS_PATH = "./artifacts";
export const TEMP_TYPED_CONTRACT_PATH = "./typedContract";
export const TEMP_ARTIFACTS_PATH = "./tmp_artifacts";
4 changes: 4 additions & 0 deletions packages/core/src/lib/tasks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ export async function copyTemplateFiles(
path.resolve(templatesPath, "github"),
path.resolve(projectPath, ".github")
);
await copy(
path.resolve(templatesPath, "scripts"),
path.resolve(projectPath, "scripts")
)
await copyContractTemplateFiles(contractTemplatePath, contractName, projectPath);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { expect, use } from "chai";
import chaiAsPromised from "chai-as-promised";
import {{contract_name_pascal}}Factory from "./typedContract/constructors/{{contract_name}}";
import {{contract_name_pascal}} from "./typedContract/contracts/{{contract_name}}";
import {{contract_name_pascal}}Factory from "./typedContracts/constructors/{{contract_name}}";
import {{contract_name_pascal}} from "./typedContracts/contracts/{{contract_name}}";
import { ApiPromise, WsProvider, Keyring } from "@polkadot/api";
import { KeyringPair } from "@polkadot/keyring/types";

Expand Down
78 changes: 78 additions & 0 deletions packages/templates/src/templates/scripts/00_deploy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { ApiPromise, WsProvider } from "@polkadot/api";

// `swanky script run 00_deploy` will run this script.

// User-defined script to run.
// This is just an deploy contract example, you can change it freely.

import {
getSwankyConfig,
AccountData,
ChainAccount,
Encrypted,
decrypt,
resolveNetworkUrl
} from "@astar-network/swanky-core";
import FlipperFactory from "../typedContracts/flipper/constructors/flipper";
import Flipper from "../typedContracts/flipper/contracts/flipper";
import inquirer from "inquirer";
import chalk from "chalk";

// Change account alias to use
const accountName = "alice";

// Change network name to deploy to
const networkName = "local";

async function main() {
const config = await getSwankyConfig();

// Keyring settings
const accountData = config.accounts.find(
(account: AccountData) => account.alias === accountName
);
if (!accountData) {
throw new Error("Provided account alias not found in swanky.config.json");
}

const mnemonic = accountData.isDev
? (accountData.mnemonic as string)
: decrypt(
accountData.mnemonic as Encrypted,
(
await inquirer.prompt([
{
type: "password",
message: `Enter password for ${chalk.yellowBright(accountData.alias)}: `,
name: "password",
},
])
).password
);

const deployer = new ChainAccount(mnemonic).pair;

// Network settings
const networkUrl = resolveNetworkUrl(config, networkName ?? "");
const wsProvider = new WsProvider(networkUrl);
const api = await ApiPromise.create({ provider: wsProvider });

// Deploy flipper contract whose initial state is set to `true`.
const flipperFactory = new FlipperFactory(api, deployer);
const initialState = true;

const contract = new Flipper(
(await flipperFactory.new(initialState)).address,
deployer,
api
);

console.log(`Flipper with initial state \`true\` deployed to ${contract.address}`);

await api.disconnect();
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});