Skip to content

Commit

Permalink
feat: add env command (#178)
Browse files Browse the repository at this point in the history
- created a new `env` command
- moved the `check` functionality under `env` command
- implemented an `install` command to install all or individual deps
- added the *env* section to *swanky.config* on `init` with default
version from docker base-image
- implemented the edit flow for the *env* section in *swanky.config*
  • Loading branch information
ipapandinas authored Mar 7, 2024
1 parent f1081b5 commit 7b0ccbf
Show file tree
Hide file tree
Showing 12 changed files with 336 additions and 182 deletions.
50 changes: 34 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,6 @@ USAGE
* [`swanky account create`](#swanky-account-create)
* [`swanky account list`](#swanky-account-list)
* [`swanky account ls`](#swanky-account-ls)
* [`swanky check`](#swanky-check)
* [`swanky contract compile [CONTRACTNAME]`](#swanky-contract-compile-contractname)
* [`swanky contract deploy CONTRACTNAME`](#swanky-contract-deploy-contractname)
* [`swanky contract explain CONTRACTNAME`](#swanky-contract-explain-contractname)
Expand All @@ -208,6 +207,8 @@ USAGE
* [`swanky contract test [CONTRACTNAME]`](#swanky-contract-test-contractname)
* [`swanky contract tx CONTRACTNAME MESSAGENAME`](#swanky-contract-tx-contractname-messagename)
* [`swanky contract typegen CONTRACTNAME`](#swanky-contract-typegen-contractname)
* [`swanky env check`](#swanky-env-check)
* [`swanky env install`](#swanky-env-install)
* [`swanky help [COMMANDS]`](#swanky-help-commands)
* [`swanky init PROJECTNAME`](#swanky-init-projectname)
* [`swanky node install`](#swanky-node-install)
Expand Down Expand Up @@ -277,21 +278,6 @@ ALIASES
$ swanky account ls
```

## `swanky check`

Check installed package versions and compatibility

```
USAGE
$ swanky check [-v]
FLAGS
-v, --verbose Display more info in the result logs
DESCRIPTION
Check installed package versions and compatibility
```

_See code: [dist/commands/check/index.ts](https:/AstarNetwork/swanky-cli/blob/v3.1.0-beta.0/dist/commands/check/index.ts)_

## `swanky contract compile [CONTRACTNAME]`
Expand Down Expand Up @@ -456,6 +442,38 @@ DESCRIPTION
Generate types from compiled contract metadata
```

## `swanky env check`

Check installed package versions and compatibility

```
USAGE
$ swanky env check [-v]
FLAGS
-v, --verbose Display more info in the result logs
DESCRIPTION
Check installed package versions and compatibility
```

## `swanky env install`

Install dev dependencies

```
USAGE
$ swanky env install [-a] [-d <dependencies>]
FLAGS
-a, --all Set all to true to install all dev dependencies specified in the swanky config
-d, --deps=<dependencies>... [default: ] Specific dev dependencies to install in the format <dependency@version>
-v, --verbose Display more info in the result logs
DESCRIPTION
Install dev dependencies
```

## `swanky help [COMMANDS]`

Display help for swanky.
Expand Down
6 changes: 3 additions & 3 deletions base-image/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ RUN curl -L https:/swankyhub/swanky-cli/releases/download/v3.1.0-bet
# Install Rustup and Rust, additional components, packages, and verify the installations
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && \
/bin/bash -c "source $HOME/.cargo/env && \
rustup install 1.72 && \
rustup default 1.72 && \
rustup install 1.76 && \
rustup default 1.76 && \
rustup component add rust-src && \
rustup target add wasm32-unknown-unknown && \
cargo install cargo-dylint dylint-link && \
cargo install cargo-contract --version 4.0.0-rc.1 && \
cargo install cargo-contract --version 4.0.0-rc.2 && \
rustc --version"

# Install Yarn 1.x
Expand Down
19 changes: 9 additions & 10 deletions src/commands/check/index.ts → src/commands/env/check.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Listr } from "listr2";
import { commandStdoutOrNull, extractCargoContractVersion } from "../../lib/index.js";
import { extractCargoContractVersion, extractCargoDylintVersion, extractCargoNightlyVersion, extractCargoVersion, extractRustVersion } from "../../lib/index.js";
import { SwankyConfig } from "../../types/index.js";
import { pathExistsSync, writeJson } from "fs-extra/esm";
import { readFileSync } from "fs";
Expand Down Expand Up @@ -36,7 +36,7 @@ interface Ctx {
looseDefinitionDetected: boolean;
}

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

static flags = {
Expand Down Expand Up @@ -79,7 +79,7 @@ export default class Check extends SwankyCommand<typeof Check> {
{
title: "Check Rust",
task: async (ctx, task) => {
ctx.versions.tools.rust = commandStdoutOrNull("rustc --version")?.match(/rustc (.*) \((.*)/)?.[1];
ctx.versions.tools.rust = extractRustVersion();
if (!ctx.versions.tools.rust) {
throw new Error("Rust is not installed");
}
Expand All @@ -90,7 +90,7 @@ export default class Check extends SwankyCommand<typeof Check> {
{
title: "Check cargo",
task: async (ctx, task) => {
ctx.versions.tools.cargo = commandStdoutOrNull("cargo -V")?.match(/cargo (.*) \((.*)/)?.[1];
ctx.versions.tools.cargo = extractCargoVersion();
if (!ctx.versions.tools.cargo) {
throw new Error("Cargo is not installed");
}
Expand All @@ -101,7 +101,7 @@ export default class Check extends SwankyCommand<typeof Check> {
{
title: "Check cargo nightly",
task: async (ctx, task) => {
ctx.versions.tools.cargoNightly = commandStdoutOrNull("cargo +nightly -V")?.match(/cargo (.*)-nightly \((.*)/)?.[1];
ctx.versions.tools.cargoNightly = extractCargoNightlyVersion();
if (!ctx.versions.tools.cargoNightly) {
throw new Error("Cargo nightly is not installed");
}
Expand All @@ -112,7 +112,7 @@ export default class Check extends SwankyCommand<typeof Check> {
{
title: "Check cargo dylint",
task: async (ctx, task) => {
ctx.versions.tools.cargoDylint = commandStdoutOrNull("cargo dylint -V")?.match(/cargo-dylint (.*)/)?.[1];
ctx.versions.tools.cargoDylint = extractCargoDylintVersion();
if (!ctx.versions.tools.cargoDylint) {
throw new Warn("Cargo dylint is not installed");
}
Expand All @@ -123,12 +123,11 @@ export default class Check extends SwankyCommand<typeof Check> {
{
title: "Check cargo-contract",
task: async (ctx, task) => {
const cargoContractVersion = extractCargoContractVersion();
ctx.versions.tools.cargoContract = cargoContractVersion;
if (!cargoContractVersion) {
ctx.versions.tools.cargoContract = extractCargoContractVersion();
if (!ctx.versions.tools.cargoContract) {
throw new Error("Cargo contract is not installed");
}
task.title = `Check cargo-contract: ${cargoContractVersion}`;
task.title = `Check cargo-contract: ${ctx.versions.tools.cargoContract}`;
},
exitOnError: false,
},
Expand Down
70 changes: 70 additions & 0 deletions src/commands/env/install.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Flags } from "@oclif/core";
import { SwankyCommand } from "../../lib/swankyCommand.js";
import { InputError } from "../../lib/errors.js";
import { installCliDevDeps } from "../../lib/tasks.js";
import { SUPPORTED_DEPS } from "../../lib/consts.js";
import { DependencyName, SwankyConfig, getSwankyConfig } from "../../index.js";
import { ConfigBuilder } from "../../lib/config-builder.js";

export class Install extends SwankyCommand<typeof Install> {
static flags = {
deps: Flags.string({
required: false,
description: `Install the specified dev dependency name and version in the format <dependency@version>. The following options are supported: ${Object.keys(
SUPPORTED_DEPS
).join(", ")}. For installing rust nightly version run: env install --deps rust@nightly`,
multiple: true,
default: [],
char: "d",
}),
};

async run(): Promise<void> {
const { flags } = await this.parse(Install);

const localConfig = getSwankyConfig('local') as SwankyConfig;
const depsToInstall = flags.deps.length > 0 ? this.parseDeps(flags.deps) : localConfig.env;

if (Object.keys(depsToInstall).length === 0) {
this.log("No dependencies to install.");
return;
}

await this.installDeps(depsToInstall);

if (flags.deps.length > 0) {
await this.updateLocalConfig(depsToInstall);
}

this.log("Swanky Dev Dependencies Installed successfully");
}

parseDeps(deps: string[]): Record<string, string> {
return deps.reduce((acc, dep) => {
const [key, value] = dep.split('@');
if (!Object.keys(SUPPORTED_DEPS).includes(key)) {
throw new InputError(`Unsupported dependency '${key}'. Supported: ${Object.keys(SUPPORTED_DEPS).join(", ")}`);
}
acc[key] = value || 'latest';
return acc;
}, {} as Record<string, string>);
}

async installDeps(dependencies: Record<string, string>) {
for (const [dep, version] of Object.entries(dependencies)) {
await this.spinner.runCommand(
() => installCliDevDeps(this.spinner, dep as DependencyName, version),
`Installing ${dep}@${version}`
);
}
}

async updateLocalConfig(newDeps: Record<string, string>): Promise<void> {
await this.spinner.runCommand(async () => {
const newLocalConfig = new ConfigBuilder(getSwankyConfig('local'))
.updateEnv(newDeps)
.build();
await this.storeConfig(newLocalConfig, 'local');
}, "Updating Swanky config with new Dev Dependencies...");
}
}
2 changes: 1 addition & 1 deletion src/commands/init/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export class Init extends SwankyCommand<typeof Init> {
this.taskQueue.push({
task: installDeps,
args: [this.projectPath],
runningMessage: "Installing dependencies",
runningMessage: "Installing NPM dependencies...",
shouldExitOnError: false,
});

Expand Down
48 changes: 46 additions & 2 deletions src/lib/command-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,12 @@ import {
DEFAULT_CONFIG_NAME,
DEFAULT_CONFIG_FOLDER_NAME,
DEFAULT_NODE_INFO,
DEFAULT_RUST_DEP_VERSION,
DEFAULT_CARGO_CONTRACT_DEP_VERSION,
DEFAULT_CARGO_DYLINT_DEP_VERSION,
} from "./consts.js";
import { SwankyConfig, SwankySystemConfig } from "../types/index.js";
import { ConfigError, FileError } from "./errors.js";
import { ConfigError, FileError, ProcessError } from "./errors.js";
import { userInfo } from "os";
import { existsSync } from "fs";

Expand Down Expand Up @@ -150,7 +153,7 @@ export function ensureAccountIsSet(account: string | undefined, config: SwankyCo
}
}

export function buildSwankyConfig() {
export function buildSwankyConfig() {
return {
node: {
localPath: "",
Expand Down Expand Up @@ -180,6 +183,11 @@ export function buildSwankyConfig() {
shibuya: { url: DEFAULT_SHIBUYA_NETWORK_URL },
},
contracts: {},
env: {
rust: extractRustVersion() ?? DEFAULT_RUST_DEP_VERSION,
"cargo-dylint": extractCargoDylintVersion() ?? DEFAULT_CARGO_DYLINT_DEP_VERSION,
"cargo-contract": extractCargoContractVersion() ?? DEFAULT_CARGO_CONTRACT_DEP_VERSION,
},
};
}

Expand All @@ -206,4 +214,40 @@ export function configName(): string {
}

return process.env.SWANKY_CONFIG?.split("/").pop() ?? DEFAULT_CONFIG_NAME;
}

export function extractVersion(command: string, regex: RegExp) {
const output = commandStdoutOrNull(command);
if (!output) {
return null;
}

const match = output.match(regex);
if (!match) {
throw new ProcessError(
`Unable to determine version from command '${command}'. Please verify its installation.`
);
}

return match[1];
}

export function extractRustVersion() {
return extractVersion("rustc --version", /rustc (.*) \((.*)/);
}

export function extractCargoVersion() {
return extractVersion("cargo -V", /cargo (.*) \((.*)/);
}

export function extractCargoNightlyVersion() {
return extractVersion("cargo +nightly -V", /cargo (.*)-nightly \((.*)/);
}

export function extractCargoDylintVersion() {
return extractVersion("cargo dylint -V", /cargo-dylint (.*)/);
}

export function extractCargoContractVersion() {
return extractVersion("cargo contract -V", /cargo-contract-contract (\d+\.\d+\.\d+(?:-[\w.]+)?)(?:-unknown-[\w-]+)/);
}
7 changes: 7 additions & 0 deletions src/lib/config-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ export class ConfigBuilder<T extends SwankySystemConfig | SwankyConfig> {
return this;
}

updateEnv(env: Record<string, string>): ConfigBuilder<T> {
if ("env" in this.config) {
this.config.env = {...this.config.env, ...env};
}
return this;
}

updateContracts(contracts: SwankyConfig["contracts"]): ConfigBuilder<T> {
if ("contracts" in this.config) {
this.config.contracts = { ...contracts };
Expand Down
10 changes: 10 additions & 0 deletions src/lib/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ export const DEFAULT_CONFIG_NAME = "swanky.config.json";
export const ARTIFACTS_PATH = "artifacts";
export const TYPED_CONTRACTS_PATH = "typedContracts";

export const DEFAULT_RUST_DEP_VERSION = "1.76.0";
export const DEFAULT_CARGO_DYLINT_DEP_VERSION = "2.6.1";
export const DEFAULT_CARGO_CONTRACT_DEP_VERSION = "4.0.0-rc.2";

export const SUPPORTED_DEPS = {
rust: DEFAULT_RUST_DEP_VERSION,
"cargo-dylint": DEFAULT_CARGO_DYLINT_DEP_VERSION,
"cargo-contract": DEFAULT_CARGO_CONTRACT_DEP_VERSION,
} as const;

export const LOCAL_FAUCET_AMOUNT = 100;
export const KEYPAIR_TYPE = "sr25519";
export const ALICE_URI = "//Alice";
Expand Down
4 changes: 2 additions & 2 deletions src/lib/swankyCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export abstract class SwankyCommand<T extends typeof Command> extends Command {
this.swankyConfig = { ...this.swankyConfig, ...systemConfig };
} catch (error) {
this.warn(
`No Swanky system config found; creating one in "/${DEFAULT_CONFIG_FOLDER_NAME}/${DEFAULT_CONFIG_NAME}}" at home directory`
`No Swanky system config found; creating one in "/${DEFAULT_CONFIG_FOLDER_NAME}/${DEFAULT_CONFIG_NAME}" at home directory`
);
await this.storeConfig(this.swankyConfig, "global");
}
Expand Down Expand Up @@ -102,7 +102,7 @@ export abstract class SwankyCommand<T extends typeof Command> extends Command {
} else {
// global
configPath = getSystemConfigDirectoryPath() + `/${DEFAULT_CONFIG_NAME}`;
if ("node" in newConfig) {
if ("accounts" in newConfig) {
// If it's a SwankyConfig, extract only the system relevant parts for the global SwankySystemConfig config
newConfig = {
defaultAccount: newConfig.defaultAccount,
Expand Down
Loading

0 comments on commit 7b0ccbf

Please sign in to comment.