Skip to content

Commit

Permalink
Merge branch 'ink-devhub-1' into feature/verifiable
Browse files Browse the repository at this point in the history
  • Loading branch information
ipapandinas authored Feb 12, 2024
2 parents f478d21 + 6701f42 commit bd56a8a
Show file tree
Hide file tree
Showing 11 changed files with 416 additions and 59 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ A newly generated project will have a `swanky.config.json` file that will get po
"node": {
"localPath": "/Users/sasapul/Work/astar/swanky-cli/temp_proj/bin/swanky-node",
"polkadotPalletVersions": "polkadot-v0.9.39",
"supportedInk": "v4.2.0"
"supportedInk": "v4.3.0"
},
"accounts": [
{
Expand Down
214 changes: 187 additions & 27 deletions src/commands/check/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import { Listr } from "listr2";
import { commandStdoutOrNull } from "../../lib/index.js";
import { SwankyConfig } from "../../types/index.js";
import { pathExistsSync, readJSON } from "fs-extra/esm";
import { pathExistsSync, readJSON, writeJson } from "fs-extra/esm";
import { readFileSync } from "fs";
import path from "node:path";
import TOML from "@iarna/toml";
import semver from "semver";
import { SwankyCommand } from "../../lib/swankyCommand.js";
import { Flags } from "@oclif/core";
import chalk from "chalk";
import { CARGO_CONTRACT_INK_DEPS } from "../../lib/cargoContractInfo.js";
import { CLIError } from "@oclif/core/lib/errors/index.js";
import Warn = CLIError.Warn;

interface Ctx {
os: {
platform: string;
architecture: string;
},
versions: {
tools: {
rust?: string | null;
Expand All @@ -17,50 +26,123 @@ interface Ctx {
cargoDylint?: string | null;
cargoContract?: string | null;
};
supportedInk?: string;
missingTools: string[];
contracts: Record<string, Record<string, string>>;
swankyNode: string | null;
};
swankyConfig?: SwankyConfig;
mismatchedVersions?: Record<string, string>;
mismatchedVersions: Record<string, string>;
looseDefinitionDetected: boolean;
}

export default class Check extends SwankyCommand<typeof Check> {
static description = "Check installed package versions and compatibility";

static flags = {
print: Flags.string({
char: "o",
description: "File to write output to",
}),
};

public async run(): Promise<void> {
const { flags } = await this.parse(Check);
const swankyNodeVersion = this.swankyConfig.node.version;
const isSwankyNodeInstalled = !!swankyNodeVersion;
const anyContracts = Object.keys(this.swankyConfig?.contracts).length > 0;
const tasks = new Listr<Ctx>([
{
title: "Check OS",
task: async (ctx, task) => {
ctx.os.platform = process.platform;
ctx.os.architecture = process.arch;
const supportedPlatforms = ["darwin", "linux"];
const supportedArch = ["arm64", "x64"];

if (!supportedPlatforms.includes(ctx.os.platform)) {
throw new Error(`Platform ${ctx.os.platform} is not supported`);
}
if (!supportedArch.includes(ctx.os.architecture)) {
throw new Error(`Architecture ${ctx.os.architecture} is not supported`);
}

task.title = `Check OS: '${ctx.os.platform}-${ctx.os.architecture}'`;
},
exitOnError: false,
},
{
title: "Check Rust",
task: async (ctx) => {
ctx.versions.tools.rust = await commandStdoutOrNull("rustc --version");
task: async (ctx, task) => {
ctx.versions.tools.rust = (await commandStdoutOrNull("rustc --version"))?.match(/rustc (.*) \((.*)/)?.[1];
if (!ctx.versions.tools.rust) {
throw new Error("Rust is not installed");
}
task.title = `Check Rust: ${ctx.versions.tools.rust}`;
},
exitOnError: false,
},
{
title: "Check cargo",
task: async (ctx) => {
ctx.versions.tools.cargo = await commandStdoutOrNull("cargo -V");
task: async (ctx, task) => {
ctx.versions.tools.cargo = (await commandStdoutOrNull("cargo -V"))?.match(/cargo (.*) \((.*)/)?.[1];
if (!ctx.versions.tools.cargo) {
throw new Error("Cargo is not installed");
}
task.title = `Check cargo: ${ctx.versions.tools.cargo}`;
},
exitOnError: false,
},
{
title: "Check cargo nightly",
task: async (ctx) => {
ctx.versions.tools.cargoNightly = await commandStdoutOrNull("cargo +nightly -V");
task: async (ctx, task) => {
ctx.versions.tools.cargoNightly = (await commandStdoutOrNull("cargo +nightly -V"))?.match(/cargo (.*)-nightly \((.*)/)?.[1];
if (!ctx.versions.tools.cargoNightly) {
throw new Error("Cargo nightly is not installed");
}
task.title = `Check cargo nightly: ${ctx.versions.tools.cargoNightly}`;
},
exitOnError: false,
},
{
title: "Check cargo dylint",
task: async (ctx) => {
ctx.versions.tools.cargoDylint = await commandStdoutOrNull("cargo dylint -V");
task: async (ctx, task) => {
ctx.versions.tools.cargoDylint = (await commandStdoutOrNull("cargo dylint -V"))?.match(/cargo-dylint (.*)/)?.[1];
if (!ctx.versions.tools.cargoDylint) {
throw new Warn("Cargo dylint is not installed");
}
task.title = `Check cargo dylint: ${ctx.versions.tools.cargoDylint}`;
},
exitOnError: false,
},
{
title: "Check cargo-contract",
task: async (ctx) => {
task: async (ctx, task) => {
ctx.versions.tools.cargoContract = await commandStdoutOrNull("cargo contract -V");
if (!ctx.versions.tools.cargoContract) {
throw new Error("Cargo contract is not installed");
}

const regex = /cargo-contract-contract (\d+\.\d+\.\d+(?:-[\w.]+)?)(?:-unknown-[\w-]+)/;
const match = ctx.versions.tools.cargoContract.match(regex);
if (match?.[1]) {
ctx.versions.tools.cargoContract = match[1];
} else {
throw new Error("Cargo contract version not found");
}
task.title = `Check cargo-contract: ${ctx.versions.tools.cargoContract}`;
},
exitOnError: false,
},
{
title: "Check swanky node",
task: async (ctx) => {
ctx.versions.swankyNode = this.swankyConfig.node.version !== "" ? this.swankyConfig.node.version : null;
},
},
{
title: "Read ink dependencies",
enabled: anyContracts,
task: async (ctx) => {
const swankyConfig = await readJSON("swanky.config.json");
ctx.swankyConfig = swankyConfig;
Expand Down Expand Up @@ -89,44 +171,122 @@ export default class Check extends SwankyCommand<typeof Check> {
},
},
{
title: "Verify ink version",
title: "Verify ink version compatibility with Swanky node",
skip: (ctx) => Object.keys(ctx.versions.contracts).length === 0,
enabled: anyContracts && isSwankyNodeInstalled,
task: async (ctx) => {
const supportedInk = ctx.swankyConfig?.node.supportedInk;

const supportedInk = ctx.swankyConfig!.node.supportedInk;
const mismatched: Record<string, string> = {};
Object.entries(ctx.versions.contracts).forEach(([contract, contractData]) => {
if (Object.prototype.hasOwnProperty.call(contractData, "ink")) {
const version = contractData.ink;
if (version && semver.gt(version, supportedInk!)) {
Object.entries(ctx.versions.contracts).forEach(([contract, inkDependencies]) => {
Object.entries(inkDependencies).forEach(([depName, version]) => {
if (semver.gt(version, supportedInk)) {
mismatched[
`${contract}-ink`
] = `Version of ink (${version}) in ${contract} is higher than supported ink version (${supportedInk})`;
`${contract}-${depName}`
] = `Version of ${depName} (${version}) in ${chalk.yellowBright(contract)} is higher than supported ink version (${supportedInk}) in current Swanky node version (${swankyNodeVersion}). A Swanky node update can fix this warning.`;
}

if (!(version.startsWith("=") || version.startsWith("v"))) {
if (version.startsWith(">") || version.startsWith("<") || version.startsWith("^") || version.startsWith("~")) {
ctx.looseDefinitionDetected = true;
}
}
});

Check failure on line 192 in src/commands/check/index.ts

View workflow job for this annotation

GitHub Actions / build-check

',' expected.

ctx.mismatchedVersions = mismatched;
if (Object.entries(mismatched).length > 0) {
throw new Warn("Ink versions in contracts don't match the Swanky node's supported version.");
}
},
exitOnError: false,
},
{
title: "Verify cargo contract compatibility",
skip: (ctx) => !ctx.versions.tools.cargoContract,
enabled: anyContracts,
task: async (ctx) => {
const cargoContractVersion = ctx.versions.tools.cargoContract!;
const dependencyIdx = CARGO_CONTRACT_INK_DEPS.findIndex((dep) =>
semver.satisfies(cargoContractVersion.replace(/-.*$/, ""), `>=${dep.minCargoContractVersion}`)
);

if (dependencyIdx === -1) {
throw new Warn(`cargo-contract version ${cargoContractVersion} is not supported`);
}

const validInkVersionRange = CARGO_CONTRACT_INK_DEPS[dependencyIdx].validInkVersionRange;
const minCargoContractVersion = dependencyIdx === 0
? CARGO_CONTRACT_INK_DEPS[dependencyIdx].minCargoContractVersion
: CARGO_CONTRACT_INK_DEPS[dependencyIdx - 1].minCargoContractVersion

const mismatched: Record<string, string> = {};
Object.entries(ctx.versions.contracts).forEach(([contract, inkPackages]) => {
Object.entries(inkPackages).forEach(([inkPackage, version]) => {
if (!semver.satisfies(version, validInkVersionRange)) {
mismatched[
`${contract}-${inkPackage}`
] = `Version of ${inkPackage} (${version}) in ${chalk.yellowBright(contract)} requires cargo-contract version >=${minCargoContractVersion}, but version ${cargoContractVersion} is installed`;
}
});
});

ctx.mismatchedVersions = { ...ctx.mismatchedVersions, ...mismatched };
if (Object.entries(mismatched).length > 0) {
throw new Warn("cargo-contract version mismatch");
}
},
exitOnError: false,
},
{
title: "Check for missing tools",
task: async (ctx) => {
const missingTools: string[] = [];
for (const [toolName, toolVersion] of Object.entries(ctx.versions.tools)) {
if (!toolVersion) {
missingTools.push(toolName);
if (toolName === "cargoDylint") this.warn("Cargo dylint is not installed");
else this.error(`${toolName} is not installed`);
}
}
ctx.versions.missingTools = missingTools;
if (Object.entries(missingTools).length > 0) {
throw new Warn(`Missing tools: ${missingTools.join(", ")}`);
}
},
exitOnError: false,
},
]);

const context = await tasks.run({
versions: { tools: {}, contracts: {} },
os: { platform: "", architecture: "" },
versions: {
tools: {},
missingTools: [],
contracts: {},
swankyNode: swankyNodeVersion || null,
},
looseDefinitionDetected: false,
mismatchedVersions: {}
});
console.log(context.versions);
Object.values(context.mismatchedVersions as any).forEach((mismatch) =>
console.error(`[ERROR] ${mismatch as string}`)
);

Object.values(context.mismatchedVersions).forEach((mismatch) => this.warn(mismatch));

if (context.looseDefinitionDetected) {
console.log(`\n[WARNING]Some of the ink dependencies do not have a fixed version.
this.warn(`Some of the ink dependencies do not have a fixed version.
This can lead to accidentally installing version higher than supported by the node.
Please use "=" to install a fixed version (Example: "=3.0.1")
`);
}

const output = {
...context.os,
...context.versions
}

const filePath = flags.print;
if (filePath !== undefined) {
await this.spinner.runCommand(async () => {
writeJson(filePath, output, { spaces: 2 });
}, `Writing output to file ${chalk.yellowBright(filePath)}`);
}
}
}

Expand Down
35 changes: 26 additions & 9 deletions src/commands/init/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { execaCommand, execaCommandSync } from "execa";
import { paramCase, pascalCase, snakeCase } from "change-case";
import inquirer from "inquirer";
import TOML from "@iarna/toml";
import { choice, email, name, pickTemplate } from "../../lib/prompts.js";
import { choice, email, name, pickNodeVersion, pickTemplate } from "../../lib/prompts.js";
import {
checkCliDependencies,
copyCommonTemplateFiles,
Expand All @@ -15,12 +15,11 @@ import {
installDeps,
ChainAccount,
processTemplates,
swankyNode,
getTemplates,
getTemplates, swankyNodeVersions,
} from "../../lib/index.js";
import {
DEFAULT_ASTAR_NETWORK_URL,
DEFAULT_NETWORK_URL,
DEFAULT_NETWORK_URL, DEFAULT_NODE_INFO,
DEFAULT_SHIBUYA_NETWORK_URL,
DEFAULT_SHIDEN_NETWORK_URL,
} from "../../lib/consts.js";
Expand Down Expand Up @@ -93,11 +92,13 @@ export class Init extends SwankyCommand<typeof Init> {
}
projectPath = "";


configBuilder: Partial<SwankyConfig> = {
node: {
localPath: "",
polkadotPalletVersions: swankyNode.polkadotPalletVersions,
supportedInk: swankyNode.supportedInk,
polkadotPalletVersions: "",
supportedInk: "",
version: "",
},
accounts: [],
networks: {
Expand Down Expand Up @@ -161,12 +162,28 @@ export class Init extends SwankyCommand<typeof Init> {
choice("useSwankyNode", "Do you want to download Swanky node?"),
]);
if (useSwankyNode) {
const versions = Array.from(swankyNodeVersions.keys());
let nodeVersion = DEFAULT_NODE_INFO.version;
await inquirer.prompt([
pickNodeVersion(versions),
]).then((answers) => {
nodeVersion = answers.version;
});

const nodeInfo = swankyNodeVersions.get(nodeVersion)!;

this.taskQueue.push({
task: downloadNode,
args: [this.projectPath, swankyNode, this.spinner],
args: [this.projectPath, nodeInfo, this.spinner],
runningMessage: "Downloading Swanky node",
callback: (result) =>
this.configBuilder.node ? (this.configBuilder.node.localPath = result) : null,
callback: (result) => {
this.configBuilder.node = {
supportedInk: nodeInfo.supportedInk,
polkadotPalletVersions: nodeInfo.polkadotPalletVersions,
version: nodeInfo.version,
localPath: result,
};
}
});
}
}
Expand Down
Loading

0 comments on commit bd56a8a

Please sign in to comment.