Skip to content

Commit

Permalink
Improvements to CLI managed service commands (#2542)
Browse files Browse the repository at this point in the history
* Improvements to CLI managed service commands

* Tidy up

* Fix build issues

* Update CI test env vars

* Various minor improvements

* Minor changes

* Update changelog

* Remove dictionary endpoint resolution from deployment commands

* Revert change
  • Loading branch information
stwiname authored Sep 15, 2024
1 parent b8cab60 commit f88953c
Show file tree
Hide file tree
Showing 19 changed files with 360 additions and 554 deletions.
7 changes: 3 additions & 4 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ jobs:
code-style:
name: code-style
runs-on: ubuntu-latest
env:
SUBQL_ACCESS_TOKEN: ${{ secrets.SUBQL_ACCESS_TOKEN }}
SUBQL_ACCESS_TOKEN_TEST: ${{ secrets.SUBQL_ACCESS_TOKEN_TEST }}
SUBQL_ORG_TEST: ${{ secrets.SUBQL_ORG_TEST }}
steps:
- uses: actions/checkout@v4
- name: Setup Node.js environment
Expand Down Expand Up @@ -46,6 +42,9 @@ jobs:
DB_DATABASE: postgres
DB_HOST: localhost
DB_PORT: 5432
SUBQL_ACCESS_TOKEN: ${{ secrets.SUBQL_ACCESS_TOKEN }}
SUBQL_ACCESS_TOKEN_TEST: ${{ secrets.SUBQL_ACCESS_TOKEN_TEST }}
SUBQL_ORG_TEST: ${{ secrets.SUBQL_ORG_TEST }}
steps:
- uses: actions/checkout@v4

Expand Down
5 changes: 5 additions & 0 deletions packages/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Fixed
- deployment command minor issues (#2542)

### Removed
- deprecated cli-ux dependency, switched to using inquirer and ora (#2542)

## [5.2.6] - 2024-09-09
### Changed
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
"@subql/common": "workspace:*",
"@subql/utils": "workspace:*",
"boxen": "5.1.2",
"cli-ux": "^6.0.9",
"ejs": "^3.1.10",
"fs-extra": "^11.2.0",
"fuzzy": "^0.1.3",
"glob": "^10.4",
"json5": "^2.2.3",
"node-fetch": "2.7.0",
"ora": "^5.4.1",
"rimraf": "^5.0.10",
"semver": "^7.6.3",
"simple-git": "^3.25.0",
Expand Down
12 changes: 0 additions & 12 deletions packages/cli/src/commands/deployment/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@ import chalk from 'chalk';
import {ROOT_API_URL_PROD} from '../../constants';
import {
DefaultDeployFlags,
dictionaryEndpoints,
executeProjectDeployment,
generateDeploymentChain,
imageVersions,
ipfsCID_validate,
processEndpoints,
projectsInfo,
splitEndpoints,
} from '../../controller/deploy-controller';
Expand Down Expand Up @@ -54,16 +52,6 @@ export default class Deploy extends Command {
flags.endpoint = await input({message: 'Enter endpoint', required: true});
}

if (!flags.dict) {
assert(validator.chainId, 'Please set chainId in your project');
const validateDictEndpoint = processEndpoints(await dictionaryEndpoints(ROOT_API_URL_PROD), validator.chainId);
if (!flags.useDefaults && !validateDictEndpoint) {
flags.dict = await input({message: 'Enter dictionary', default: validateDictEndpoint});
} else {
flags.dict = validateDictEndpoint;
}
}

if (!flags.indexerVersion) {
assert(validator.manifestRunner, 'Please set manifestRunner in your project');
try {
Expand Down
72 changes: 26 additions & 46 deletions packages/cli/src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,15 @@
import assert from 'assert';
import fs from 'fs';
import path from 'path';
import {URL} from 'url';
import {search, confirm, input} from '@inquirer/prompts';
import {Args, Command, Flags} from '@oclif/core';
import {NETWORK_FAMILY} from '@subql/common';
import chalk from 'chalk';
import cli from 'cli-ux';
import fuzzy from 'fuzzy';
import ora from 'ora';
import {
installDependencies,
cloneProjectTemplate,
cloneProjectGit,
readDefaults,
prepare,
prepareProjectScaffold,
Expand All @@ -29,33 +27,12 @@ import Generate from './codegen/generate';

// Helper function for fuzzy search on prompt input
function filterInput<T>(arr: T[]) {
return (input: string | undefined, opt: {signal: any}): Promise<ReadonlyArray<{value: T}>> => {
return (input: string | undefined): Promise<ReadonlyArray<{value: T}>> => {
input ??= '';
return Promise.resolve(fuzzy.filter(input, arr).map((r) => ({value: r.original})));
};
}

async function promptValidRemoteAndBranch(): Promise<string[]> {
let isValid = false;
let remote: string | undefined;
while (!isValid) {
try {
remote = (await cli.prompt('Custom template git remote', {
required: true,
})) as string;
new URL(remote);
isValid = true;
} catch (e) {
console.log(`Not a valid git remote URL: '${remote}', try again`);
continue;
}
}
const branch = await cli.prompt('Custom template git branch', {
required: true,
});
return [remote, branch];
}

export default class Init extends Command {
static description = 'Initialize a scaffold subquery project';

Expand All @@ -80,7 +57,11 @@ export default class Init extends Command {
const project = {} as ProjectSpecBase;
project.name = args.projectName
? args.projectName
: await cli.prompt('Project name', {default: 'subql-starter', required: true});
: await input({
message: 'Project name',
default: 'subql-starter',
required: true,
});
if (fs.existsSync(path.join(location, `${project.name}`))) {
throw new Error(`Directory ${project.name} exists, try another project name`);
}
Expand Down Expand Up @@ -162,40 +143,39 @@ export default class Init extends Command {
}
}

async cloneCustomRepo(project: ProjectSpecBase, projectPath: string, location: string): Promise<void> {
const [gitRemote, gitBranch] = await promptValidRemoteAndBranch();
projectPath = await cloneProjectGit(location, project.name, gitRemote, gitBranch);
}

async setupProject(project: ProjectSpecBase, projectPath: string, flags: any): Promise<void> {
async setupProject(
project: ProjectSpecBase,
projectPath: string,
flags: {npm: boolean; 'install-dependencies': boolean}
): Promise<void> {
const [defaultEndpoint, defaultAuthor, defaultDescription] = await readDefaults(projectPath);

project.endpoint = !Array.isArray(defaultEndpoint) ? [defaultEndpoint] : defaultEndpoint;
const userInput = await cli.prompt('RPC endpoint:', {
const userInput = await input({
message: 'RPC endpoint:',
default: defaultEndpoint[0] ?? 'wss://polkadot.api.onfinality.io/public-ws',
required: false,
});
if (!project.endpoint.includes(userInput)) {
(project.endpoint as string[]).push(userInput);
}
const descriptionHint = defaultDescription.substring(0, 40).concat('...');
project.author = await cli.prompt('Author', {required: true, default: defaultAuthor});
project.description = await cli
.prompt('Description', {
required: false,
default: descriptionHint,
})
.then((description) => {
return description === descriptionHint ? defaultDescription : description;
});
project.author = await input({message: 'Author', required: true, default: defaultAuthor});
project.description = await input({
message: 'Description',
required: false,
default: descriptionHint,
}).then((description) => {
return description === descriptionHint ? defaultDescription : description;
});

cli.action.start('Preparing project');
const spinner = ora('Preparing project').start();
await prepare(projectPath, project);
cli.action.stop();
spinner.stop();
if (flags['install-dependencies']) {
cli.action.start('Installing dependencies');
const spinner = ora('Installing dependencies').start();
installDependencies(projectPath, flags.npm);
cli.action.stop();
spinner.stop();
}
this.log(`${project.name} is ready`);
}
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/src/commands/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
import assert from 'assert';
import fs, {lstatSync} from 'fs';
import path from 'path';
import {input} from '@inquirer/prompts';
import {Command, Flags} from '@oclif/core';
import {makeTempDir} from '@subql/common';
import cli from 'cli-ux';
import git from 'simple-git';
import {
DEFAULT_SUBGRAPH_MANIFEST,
Expand Down Expand Up @@ -41,8 +41,8 @@ export default class Migrate extends Command {
const {flags} = await this.parse(Migrate);
const {file, gitSubDirectory, output} = flags;

const subgraphPath = file ?? (await cli.prompt('Subgraph project path, local or git', {required: true}));
const subqlPath = output ?? (await cli.prompt('SubQuery project path, local or git', {required: true}));
const subgraphPath = file ?? (await input({message: 'Subgraph project path, local or git', required: true}));
const subqlPath = output ?? (await input({message: 'SubQuery project path, local or git', required: true}));

const gitMatch = extractGitInfo(subgraphPath);
// will return false if directory not exist
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/multi-chain/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
// SPDX-License-Identifier: GPL-3.0

import assert from 'assert';
import {input} from '@inquirer/prompts';
import {Command, Flags} from '@oclif/core';
import {cli} from 'cli-ux';
import {addChain} from '../../controller/add-chain-controller';
import {resolveToAbsolutePath} from '../../utils';

Expand All @@ -22,7 +22,7 @@ export default class MultiChainAdd extends Command {
let {chainManifestPath} = flags;

if (!chainManifestPath) {
chainManifestPath = await cli.prompt('Enter the path to the new chain manifest');
chainManifestPath = await input({message: 'Enter the path to the new chain manifest'});
}
assert(chainManifestPath, 'Chain manifest path is required');

Expand Down
30 changes: 8 additions & 22 deletions packages/cli/src/commands/multi-chain/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,19 @@ import {input} from '@inquirer/prompts';
import {Command, Flags} from '@oclif/core';
import {getMultichainManifestPath, getProjectRootAndManifest} from '@subql/common';
import chalk from 'chalk';
import cli from 'cli-ux';
import ora from 'ora';
import YAML from 'yaml';
import {ROOT_API_URL_PROD} from '../../constants';
import {
DefaultDeployFlags,
dictionaryEndpoints,
executeProjectDeployment,
generateDeploymentChain,
ipfsCID_validate,
processEndpoints,
projectsInfo,
splitMultichainDataFields,
} from '../../controller/deploy-controller';
import {getDirectoryCid, uploadToIpfs} from '../../controller/publish-controller';
import {MultichainDataFieldType, V3DeploymentIndexerType} from '../../types';
import {V3DeploymentIndexerType} from '../../types';
import {addV, checkToken, resolveToAbsolutePath, valueOrPrompt} from '../../utils';
import {promptImageVersion} from '../deployment/deploy';

Expand Down Expand Up @@ -59,14 +57,14 @@ export default class MultiChainDeploy extends Command {
multichainManifestPath = path.join(project.root, multichainManifestPath);
const multichainManifestObject = YAML.parse(fs.readFileSync(multichainManifestPath, 'utf8'));

cli.action.start('Uploading project to IPFS');
const spinner = ora('Uploading project to IPFS').start();
const fileToCidMap = await uploadToIpfs(fullPaths, authToken.trim(), multichainManifestPath, flags.ipfs).catch(
(e) => {
cli.action.stop();
spinner.fail(e.message);
this.error(e);
}
);
cli.action.stop('DONE');
spinner.succeed('Uploaded project to IPFS');

flags.org = await valueOrPrompt(flags.org, 'Enter organisation', 'Organisation is required');
flags.projectName = await valueOrPrompt(flags.projectName, 'Enter project name', 'Project name is required');
Expand All @@ -78,9 +76,9 @@ export default class MultiChainDeploy extends Command {
const projectInfo = await projectsInfo(authToken, flags.org, flags.projectName, ROOT_API_URL_PROD, flags.type);
const chains: V3DeploymentIndexerType[] = [];

const endpoints: MultichainDataFieldType = splitMultichainDataFields(flags.endpoint);
const dictionaries: MultichainDataFieldType = splitMultichainDataFields(flags.dict);
const indexerVersions: MultichainDataFieldType = splitMultichainDataFields(flags.indexerVersion);
const endpoints = splitMultichainDataFields(flags.endpoint);
const dictionaries = splitMultichainDataFields(flags.dict);
const indexerVersions = splitMultichainDataFields(flags.indexerVersion);

if (!flags.queryVersion) {
try {
Expand Down Expand Up @@ -139,18 +137,6 @@ export default class MultiChainDeploy extends Command {
});
}

if (!dictionaries[validator.chainId]) {
const validateDictEndpoint = processEndpoints(await dictionaryEndpoints(ROOT_API_URL_PROD), validator.chainId);
if (!flags.useDefaults && !validateDictEndpoint) {
dictionaries[validator.chainId] = await input({
message: `Enter dictionary for ${multichainProjectPath}`,
required: false,
});
} else if (validateDictEndpoint) {
dictionaries[validator.chainId] = validateDictEndpoint;
}
}

chains.push(
generateDeploymentChain({
cid: multichainProjectCid,
Expand Down
32 changes: 13 additions & 19 deletions packages/cli/src/commands/project/create-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,9 @@ export default class Create_project extends Command {
static flags = {
org: Flags.string({description: 'Enter organization name'}),
projectName: Flags.string({description: 'Enter project name'}),
gitRepo: Flags.string({description: 'Enter git repository'}),

logoURL: Flags.string({description: 'Enter logo URL', default: '', required: false}),
subtitle: Flags.string({description: 'Enter subtitle', default: '', required: false}),
description: Flags.string({description: 'Enter description', default: '', required: false}),
apiVersion: Flags.string({description: 'Enter api version', default: '2', required: false}),
dedicatedDB: Flags.string({description: 'Enter dedicated DataBase', required: false}),
projectType: Flags.string({
description: 'Enter project type [subquery|subgraph]',
Expand All @@ -30,7 +27,7 @@ export default class Create_project extends Command {
async run(): Promise<void> {
const {flags} = await this.parse(Create_project);

let {gitRepo, org, projectName} = flags;
let {org, projectName} = flags;
assert(
['subquery'].includes(flags.projectType),
'Invalid project type, only "subquery" is supported. Please deploy Subgraphs through the website.'
Expand All @@ -39,21 +36,18 @@ export default class Create_project extends Command {

org = await valueOrPrompt(org, 'Enter organisation', 'Organisation is required');
projectName = await valueOrPrompt(projectName, 'Enter project name', 'Project name is required');
gitRepo = await valueOrPrompt(gitRepo, 'Enter git repository', 'Git repository is required');

const result = await createProject(
org,
flags.subtitle,
flags.logoURL,
flags.projectType === 'subquery' ? 1 : 3,
projectName,
authToken,
gitRepo,
flags.description,
flags.apiVersion,
flags.dedicatedDB,
ROOT_API_URL_PROD
).catch((e) => this.error(e));

const result = await createProject(ROOT_API_URL_PROD, authToken, {
apiVersion: 'v3',
description: flags.description,
key: `${org}/${projectName}`,
logoUrl: flags.logoURL,
name: projectName,
subtitle: flags.subtitle,
dedicateDBKey: flags.dedicatedDB,
tag: [],
type: flags.projectType === 'subquery' ? 1 : 3,
}).catch((e) => this.error(e));

const [account, name] = result.key.split('/');
this.log(`Successfully created project: ${result.key}
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@

import assert from 'assert';
import path from 'path';
import {DeploymentType} from './types';

//DEPLOYMENT
export const DEFAULT_DEPLOYMENT_TYPE = 'primary';
export const DEFAULT_DEPLOYMENT_TYPE = 'primary' satisfies DeploymentType;
//PROJECT
export const ROOT_API_URL_DEV = 'https://api.thechaindata.com';
export const ROOT_API_URL_PROD = 'https://api.subquery.network';

export const BASE_PROJECT_URL = 'https://project.subquery.network';
export const BASE_PROJECT_URL = 'https://managedservice.subquery.network';

export const BASE_TEMPLATE_URl = 'https://templates.subquery.network';

Expand Down
Loading

0 comments on commit f88953c

Please sign in to comment.