Skip to content

Commit

Permalink
Multi-chain project initialization error (#2587)
Browse files Browse the repository at this point in the history
* Multi-chain project initialization error

* Update packages/cli/src/commands/init.ts

Co-authored-by: Scott Twiname <[email protected]>

* Support init the endpoint type of ProjectNetworkConfig

---------

Co-authored-by: Scott Twiname <[email protected]>
  • Loading branch information
yoozo and stwiname authored Nov 18, 2024
1 parent 6d01023 commit f378dca
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 29 deletions.
3 changes: 3 additions & 0 deletions packages/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed
- Multi-chain project initialization error

## [5.3.0] - 2024-10-21
### Changed
- Improve codegen error messages (#2567)
Expand Down
50 changes: 36 additions & 14 deletions packages/cli/src/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import path from 'path';
import {search, confirm, input} from '@inquirer/prompts';
import {Args, Command, Flags} from '@oclif/core';
import {NETWORK_FAMILY} from '@subql/common';
import {ProjectNetworkConfig} from '@subql/types-core';
import chalk from 'chalk';
import fuzzy from 'fuzzy';
import ora from 'ora';
Expand Down Expand Up @@ -128,8 +129,8 @@ export default class Init extends Command {

assert(selectedProject, 'No project selected');
const projectPath: string = await cloneProjectTemplate(location, project.name, selectedProject);

await this.setupProject(project, projectPath, flags);
const {isMultiChainProject} = await this.setupProject(project, projectPath, flags);
if (isMultiChainProject) return;

if (await validateEthereumProjectManifest(projectPath)) {
const loadAbi = await confirm({
Expand All @@ -147,17 +148,26 @@ export default class Init extends Command {
project: ProjectSpecBase,
projectPath: string,
flags: {npm: boolean; 'install-dependencies': boolean}
): Promise<void> {
const [defaultEndpoint, defaultAuthor, defaultDescription] = await readDefaults(projectPath);
): Promise<{isMultiChainProject: boolean}> {
const {
author: defaultAuthor,
description: defaultDescription,
endpoint: defaultEndpoint,
isMultiChainProject,
} = await readDefaults(projectPath);

if (!isMultiChainProject) {
const projectEndpoints: string[] = this.extractEndpoints(defaultEndpoint);
const userInput = await input({
message: 'RPC endpoint:',
default: projectEndpoints[0],
required: false,
});
if (!projectEndpoints.includes(userInput)) {
projectEndpoints.push(userInput);
}

project.endpoint = !Array.isArray(defaultEndpoint) ? [defaultEndpoint] : defaultEndpoint;
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);
project.endpoint = projectEndpoints;
}
const descriptionHint = defaultDescription.substring(0, 40).concat('...');
project.author = await input({message: 'Author', required: true, default: defaultAuthor});
Expand All @@ -170,14 +180,16 @@ export default class Init extends Command {
});

const spinner = ora('Preparing project').start();
await prepare(projectPath, project);
await prepare(projectPath, project, isMultiChainProject);
spinner.stop();
if (flags['install-dependencies']) {
const spinner = ora('Installing dependencies').start();
installDependencies(projectPath, flags.npm);
spinner.stop();
}
this.log(`${project.name} is ready`);
this.log(`${project.name} is ready${isMultiChainProject ? ' as a multi-chain project' : ''}`);

return {isMultiChainProject};
}

async createProjectScaffold(projectPath: string): Promise<void> {
Expand Down Expand Up @@ -210,4 +222,14 @@ export default class Init extends Command {
`${startBlock}`,
]);
}

extractEndpoints(endpointConfig: ProjectNetworkConfig['endpoint']): string[] {
if (typeof endpointConfig === 'string') {
return [endpointConfig];
}
if (endpointConfig instanceof Array) {
return endpointConfig;
}
return Object.keys(endpointConfig);
}
}
2 changes: 1 addition & 1 deletion packages/cli/src/controller/init-controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ describe('Cli can create project', () => {
const project = projects.find((p) => p.name === 'Polkadot-starter')!;
const projectPath = await cloneProjectTemplate(tempPath, projectSpec.name, project);
await prepare(projectPath, projectSpec);
const [, author, description] = await readDefaults(projectPath);
const {author, description} = await readDefaults(projectPath);

//spec version is not returned from readDefaults
//expect(projectSpec.specVersion).toEqual(specVersion);
Expand Down
31 changes: 25 additions & 6 deletions packages/cli/src/controller/init-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
defaultEnvLocalPath,
defaultEnvPath,
defaultGitIgnorePath,
defaultMultiChainYamlManifestPath,
defaultTSManifestPath,
defaultYamlManifestPath,
errorHandle,
Expand Down Expand Up @@ -138,12 +139,21 @@ export async function cloneProjectTemplate(
return projectPath;
}

export async function readDefaults(projectPath: string): Promise<string[]> {
export async function readDefaults(projectPath: string): Promise<{
endpoint: ProjectNetworkConfig['endpoint'];
author: string;
description: string;
isMultiChainProject: boolean;
}> {
const packageData = await fs.promises.readFile(`${projectPath}/package.json`);
const currentPackage = JSON.parse(packageData.toString());
const author: string = currentPackage.author;
const description: string = currentPackage.description;
let endpoint: ProjectNetworkConfig['endpoint'];
let isMultiChainProject = false;
const defaultTsPath = defaultTSManifestPath(projectPath);
const defaultYamlPath = defaultYamlManifestPath(projectPath);
const defaultMultiChainPath = defaultMultiChainYamlManifestPath(projectPath);

if (fs.existsSync(defaultTsPath)) {
const tsManifest = await fs.promises.readFile(defaultTsPath, 'utf8');
Expand All @@ -152,23 +162,32 @@ export async function readDefaults(projectPath: string): Promise<string[]> {
});

endpoint = extractedTsValues.endpoint ?? [];
} else {
} else if (fs.existsSync(defaultYamlPath)) {
const yamlManifest = await fs.promises.readFile(defaultYamlPath, 'utf8');
const extractedYamlValues = parseDocument(yamlManifest).toJS() as ProjectManifestV1_0_0;
endpoint = extractedYamlValues.network.endpoint;
} else if (fs.existsSync(defaultMultiChainPath)) {
endpoint = [];
isMultiChainProject = true;
} else {
throw new Error('Failed to read manifest file while preparing the project');
}

return [endpoint, currentPackage.author, currentPackage.description];
return {endpoint, author, description, isMultiChainProject};
}

export async function prepare(projectPath: string, project: ProjectSpecBase): Promise<void> {
export async function prepare(
projectPath: string,
project: ProjectSpecBase,
isMultiChainProject = false
): Promise<void> {
try {
await prepareEnv(projectPath, project);
if (!isMultiChainProject) await prepareEnv(projectPath, project);
} catch (e) {
throw new Error('Failed to prepare read or write .env file while preparing the project');
}
try {
await prepareManifest(projectPath, project);
if (!isMultiChainProject) await prepareManifest(projectPath, project);
} catch (e) {
throw new Error('Failed to prepare read or write manifest while preparing the project');
}
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright 2020-2024 SubQuery Pte Ltd authors & contributors
// SPDX-License-Identifier: GPL-3.0

import {RunnerSpecs} from '@subql/types-core';
import {ProjectNetworkConfig, RunnerSpecs} from '@subql/types-core';

export interface ProjectSpecBase {
name: string;
author: string;
description?: string;
endpoint: string[] | string;
endpoint: ProjectNetworkConfig['endpoint'];
}

export interface QueryAdvancedOpts {
Expand Down
7 changes: 1 addition & 6 deletions packages/cli/src/utils/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import {MultichainProjectManifest} from '@subql/types-core';
import * as yaml from 'js-yaml';
import * as tsNode from 'ts-node';
import {isMultichain} from './utils';

const requireScriptWrapper = (scriptPath: string, outputPath: string): string =>
`import {toJsonObject} from '@subql/common';` +
Expand Down Expand Up @@ -131,12 +132,6 @@ function getTsManifestsFromMultichain(location: string): string[] {
.map((project) => path.resolve(path.dirname(location), project));
}

function isMultichain(location: string): boolean {
const multichainContent = yaml.load(readFileSync(location, 'utf8')) as MultichainProjectManifest;

return !!multichainContent && !!multichainContent.projects;
}

function replaceTsReferencesInMultichain(location: string): void {
const multichainContent = yaml.load(readFileSync(location, 'utf8')) as MultichainProjectManifest;
multichainContent.projects = multichainContent.projects.map((project) => tsProjectYamlPath(project));
Expand Down
12 changes: 12 additions & 0 deletions packages/cli/src/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ import {
DEFAULT_ENV_LOCAL,
DEFAULT_GIT_IGNORE,
DEFAULT_MANIFEST,
DEFAULT_MULTICHAIN_MANIFEST,
DEFAULT_TS_MANIFEST,
} from '@subql/common';
import {MultichainProjectManifest} from '@subql/types-core';
import axios from 'axios';
import ejs from 'ejs';
import * as yaml from 'js-yaml';
import JSON5 from 'json5';
import {rimraf} from 'rimraf';
import {ACCESS_TOKEN_PATH} from '../constants';
Expand Down Expand Up @@ -263,3 +266,12 @@ export function copyFolderSync(source: string, target: string): void {
}
});
}

export function defaultMultiChainYamlManifestPath(projectPath: string): string {
return path.join(projectPath, DEFAULT_MULTICHAIN_MANIFEST);
}

export function isMultichain(location: string): boolean {
const multichainContent = yaml.load(readFileSync(location, 'utf8')) as MultichainProjectManifest;
return !!multichainContent && !!multichainContent.projects;
}

0 comments on commit f378dca

Please sign in to comment.