Skip to content

Commit

Permalink
Feature/init convert (#136)
Browse files Browse the repository at this point in the history
Main feature is introducing a `--convert` flag to `init` command. (#111
)
Also handled are #113 and #83 .

Some changes to how `init` works are introduced:
- all "tasks" now get pushed into the task queue and only executed after
all the options are confirmed
- config is also gathered in the global configBuilder, and written as
the last step

To test:
- have an existing project cloned to your filesystem (for example dex,
or nft from https://github.com/swanky-dapps)
- run `swanky init proj_name --convert path_to_existing_project`
- it will check for Cargo.toml with `workspace` `members` and `exclude`
fields
- it assumes `members` are contracts, and `exclude` are extra crates
- you can also specify manually
- when done, you still need to manually adjust import paths in the
contracts' Cargo.toml

Note: the converted project is not expected to work out-of-the-box when
converted, but should require minimum adjustments.
  • Loading branch information
codespool authored May 17, 2023
1 parent 56c54f5 commit 0fde90c
Show file tree
Hide file tree
Showing 26 changed files with 1,416 additions and 727 deletions.
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"useWorkspaces": true,
"version": "2.1.2",
"version": "2.2.2-alpha.0",
"npmClient": "yarn"
}
17 changes: 9 additions & 8 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ $ npm install -g @astar-network/swanky-cli
$ swanky COMMAND
running command...
$ swanky (--version|-V|-v)
@astar-network/swanky-cli/2.1.2 darwin-arm64 node-v18.10.0
@astar-network/swanky-cli/2.2.2-alpha.0 darwin-x64 node-v18.2.0
$ swanky --help [COMMAND]
USAGE
$ swanky COMMAND
Expand Down Expand Up @@ -114,7 +114,7 @@ DESCRIPTION
Check installed package versions and compatibility
```

_See code: [dist/commands/check/index.js](https://github.com/AstarNetwork/swanky-cli/blob/v2.1.2/dist/commands/check/index.js)_
_See code: [dist/commands/check/index.js](https://github.com/AstarNetwork/swanky-cli/blob/v2.2.2-alpha.0/dist/commands/check/index.js)_

## `swanky contract compile [CONTRACTNAME]`

Expand Down Expand Up @@ -160,7 +160,7 @@ DESCRIPTION

## `swanky contract explain CONTRACTNAME`

Explain contract messages based on thier metadata
Explain contract messages based on the contracts' metadata

```
USAGE
Expand All @@ -173,7 +173,7 @@ FLAGS
-v, --verbose Display more info in the result logs
DESCRIPTION
Explain contract messages based on thier metadata
Explain contract messages based on the contracts' metadata
```

## `swanky contract new CONTRACTNAME`
Expand Down Expand Up @@ -300,23 +300,24 @@ Generate a new smart contract environment

```
USAGE
$ swanky init PROJECTNAME [--swanky-node] [-t blank|erc20token|flipper|blank|flipper|psp22] [-l ask|ink]
[-v]
$ swanky init PROJECTNAME [-v] [--swanky-node] [-t blank|erc20token|flipper|blank|flipper|psp22] [-l
ask|ink] [-c <value>]
ARGUMENTS
PROJECTNAME directory name of new project
FLAGS
-c, --convert=<value> Converts an existing smart contract into a Swanky project
-l, --language=<option> <options: ask|ink>
-t, --template=<option> <options: blank|erc20token|flipper|blank|flipper|psp22>
-v, --verbose
-v, --verbose Display more info in the result logs
--swanky-node
DESCRIPTION
Generate a new smart contract environment
```

_See code: [dist/commands/init/index.js](https://github.com/AstarNetwork/swanky-cli/blob/v2.1.2/dist/commands/init/index.js)_
_See code: [dist/commands/init/index.js](https://github.com/AstarNetwork/swanky-cli/blob/v2.2.2-alpha.0/dist/commands/init/index.js)_

## `swanky node install`

Expand Down
15 changes: 10 additions & 5 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@astar-network/swanky-cli",
"version": "2.1.2",
"version": "2.2.2-alpha.0",
"description": "Astar network WASM contract CLI tool",
"author": "Astar network",
"bin": {
Expand All @@ -17,39 +17,44 @@
"/oclif.manifest.json"
],
"dependencies": {
"@astar-network/swanky-core": "^2.1.2",
"@astar-network/swanky-templates": "^2.1.1",
"@astar-network/swanky-core": "^2.2.2-alpha.0",
"@astar-network/swanky-templates": "^2.2.2-alpha.0",
"@iarna/toml": "^2.2.5",
"@oclif/core": "2.5.0",
"@oclif/plugin-help": "5.2.7",
"@oclif/plugin-plugins": "2.3.2",
"@oclif/plugin-version": "1.2.1",
"@polkadot/util": "11.0.1",
"@polkadot/util-crypto": "11.0.1",
"@types/shelljs": "0.8.11",
"chalk": "4",
"change-case": "4.1.2",
"execa": "5.1.1",
"fs-extra": "10.1.0",
"globby": "11",
"inquirer": "8.2.5",
"inquirer-fuzzy-path": "^2.3.0",
"js-toml": "^0.1.1",
"listr2": "5.0.7",
"lodash": "^4.17.21",
"mocha": "10.2.0",
"mocha-suppress-logs": "0.3.1",
"mochawesome": "7.1.3",
"semver": "7.3.8",
"shelljs": "0.8.5",
"toml": "3.0.0",
"ts-mocha": "^10.0.0",
"typescript": "4.9.5"
},
"devDependencies": {
"@oclif/test": "2.3.9",
"@types/chai": "4",
"@types/fs-extra": "9.0.13",
"@types/iarna__toml": "^2.0.2",
"@types/inquirer": "8.2.5",
"@types/inquirer-fuzzy-path": "^2.3.6",
"@types/mocha": "9.0.0",
"@types/node": "18.11.9",
"@types/semver": "7.3.10",
"@types/shelljs": "0.8.11",
"@typescript-eslint/eslint-plugin": "5.35.1",
"@typescript-eslint/parser": "5.35.1",
"chai": "4",
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/check/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Listr } from "listr2";
import { commandStdoutOrNull, ensureSwankyProject, SwankyConfig } from "@astar-network/swanky-core";
import fs = require("fs-extra");
import path = require("node:path");
import toml = require("toml");
import TOML from "@iarna/toml";
import semver = require("semver");

interface Ctx {
Expand Down Expand Up @@ -78,7 +78,7 @@ export default class Check extends Command {
encoding: "utf8",
});

const cargoToml = toml.parse(cargoTomlString);
const cargoToml = TOML.parse(cargoTomlString);

const inkDependencies = Object.entries(cargoToml.dependencies)
.filter((dependency) => dependency[0].includes("ink_"))
Expand Down
140 changes: 41 additions & 99 deletions packages/cli/src/commands/contract/compile.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,14 @@
import { Args, Command, Flags } from "@oclif/core";
import path = require("node:path");
import Files from "fs";
import fs = require("fs-extra");
import {
storeArtifacts,
ensureSwankyProject,
ContractData,
getSwankyConfig,
BuildData,
Spinner,
generateTypes,
} from "@astar-network/swanky-core";
import { ChildProcessWithoutNullStreams, spawn } from "node:child_process";
import { writeJSON, existsSync } from "fs-extra";

function getBuildCommandFor(
language: ContractData["language"],
contractPath: string,
release: boolean,
) : ChildProcessWithoutNullStreams {
if (language === "ink") {
const args = ["contract", "build", "--manifest-path", `${contractPath}/Cargo.toml`]
if (release) {
args.push("--release")
}
return spawn(
"cargo",
args
)
}
if (language === "ask") {
const args = ["asc", "--config", `${contractPath}/asconfig.json`, `${contractPath}/index.ts`];
if (release) {
args.push("-O");
args.push("--noAssert");
}
return spawn(
"npx",
args,
{ env: { ...process.env, ASK_CONFIG: `${contractPath}/askconfig.json` } }
);
}
throw new Error("Unsupported language!");
}

import { spawn } from "node:child_process";
import { pathExists } from "fs-extra";

export class CompileContract extends Command {
static description = "Compile the smart contract(s) in your contracts directory";
Expand All @@ -57,13 +22,14 @@ export class CompileContract extends Command {
release: Flags.boolean({
default: false,
char: "r",
description: "A production contract should always be build in `release` mode for building optimized wasm"
description:
"A production contract should always be build in `release` mode for building optimized wasm",
}),
all: Flags.boolean({
default: false,
char: "a",
description: "Set all to true to compile all contracts"
})
description: "Set all to true to compile all contracts",
}),
};

static args = {
Expand All @@ -79,99 +45,75 @@ export class CompileContract extends Command {
const { args, flags } = await this.parse(CompileContract);

if (args.contractName === undefined && !flags.all) {
this.error("No contracts were selected to compile")
this.error("No contracts were selected to compile");
}

await ensureSwankyProject();
const config = await getSwankyConfig();

const contractNames = [];
if (flags.all) {
for (const contractName of Object.keys(config.contracts)) {
console.log(`${contractName} contract is found`);
contractNames.push(contractName);
}
} else {
contractNames.push(args.contractName);
}

const contractNames = flags.all ? Object.keys(config.contracts) : [args.contractName];
const spinner = new Spinner();

for (const contractName of contractNames) {
const contractInfo = config.contracts[contractName];
if (!contractInfo) {
this.error(`Cannot find contract info for ${contractName} contract in swanky.config.json`);
}
const contractPath = path.resolve("contracts", contractName);
if (!existsSync(contractPath)) {
const contractPath = path.resolve("contracts", contractInfo.name);

if (!(await pathExists(contractPath))) {
this.error(`Contract folder not found at expected path`);
}

await spinner.runCommand(
const compilationResult = await spinner.runCommand(
async () => {
return new Promise<void>((resolve, reject) => {
const compile = getBuildCommandFor(contractInfo.language, contractPath, flags.release);
compile.stdout.on("data", () => spinner.ora.clear());
return new Promise<string>((resolve, reject) => {
const compileArgs = [
"contract",
"build",
"--manifest-path",
`${contractPath}/Cargo.toml`,
];
if (flags.release) {
compileArgs.push("--release");
}
const compile = spawn("cargo", compileArgs);
let outputBuffer = "";

compile.stdout.on("data", (data) => {
outputBuffer += data.toString();
spinner.ora.clear();
});
compile.stdout.pipe(process.stdout);

if (flags.verbose) {
compile.stderr.on("data", () => spinner.ora.clear());
compile.stderr.pipe(process.stdout);
}
compile.on("exit", (code) => {
if (code === 0) resolve();
else reject();
if (code === 0) {
const regex = /Your contract artifacts are ready\. You can find them in:\n(.*)/;
const match = outputBuffer.match(regex);
if (match) resolve(match[1]);
} else reject();
});
});
},
`Compiling ${contractName} contract`,
`${contractName} Contract compiled successfully`,
`${contractName} Contract compiled successfully`
);

// Depends on which language was used, the artifacts will be generated in different places at compile step.
let artifactsPath: string;
if (contractInfo.language === "ink") {
artifactsPath = path.resolve("target", "ink", `${contractName}`);
} else if (contractInfo.language === "ask") {
artifactsPath = path.resolve(contractPath, "build");
} else {
throw new Error("Unsupported language!");
}
const artifactsPath = compilationResult as string;

// Ask! build artifacts don't have `.contract` file which is just an combination of abi json and wasm blob
// `.contract` will have .source.wasm field whose value is wasm blob hex representation.
// Once Ask! support .contract file, this part will be removed.
if (contractInfo.language == "ask") {
// Unlike ink!, ask! names metadata file `metadata.json`. So, renaming it to contract name here needed for following tasks.
await fs.copyFile(
path.resolve(artifactsPath, "metadata.json"),
path.resolve(artifactsPath, `${contractName}.json`),
)
const contract = JSON.parse(Files.readFileSync(path.resolve(artifactsPath, `${contractName}.json`)).toString());
const wasmBuf = Files.readFileSync(path.resolve(artifactsPath, `${contractName}.wasm`));
const prefix = "0x"
contract.source.wasm = prefix + wasmBuf.toString("hex");
fs.writeFileSync(path.resolve(artifactsPath, `${contractName}.contract`), JSON.stringify(contract));
await fs.remove(path.resolve(artifactsPath, `${contractName}.wasm`));
}
await spinner.runCommand(async () => {
return storeArtifacts(artifactsPath, contractInfo.name, contractInfo.moduleName);
}, "Moving artifacts");

const typedContractDestPath = path.resolve("test", contractName, "typedContract");
await spinner.runCommand(
async () => await generateTypes(artifactsPath, contractName, typedContractDestPath),
async () => await generateTypes(contractInfo.name),
`Generating ${contractName} contract ts types`,
`${contractName} contract's TS types Generated successfully`
);

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

contractInfo.build = buildData;
}

await spinner.runCommand(async () => {
await writeJSON(path.resolve("swanky.config.json"), config, {
spaces: 2,
});
}, "Writing config");
}
}
Loading

0 comments on commit 0fde90c

Please sign in to comment.