From c6cbc78a47728e99e17bf8171fa699073b2d30d2 Mon Sep 17 00:00:00 2001 From: Tate Date: Tue, 12 Nov 2024 01:26:05 +0000 Subject: [PATCH 1/3] Multi-chain project initialization error --- packages/cli/CHANGELOG.md | 3 ++ packages/cli/src/commands/init.ts | 39 ++++++++++++------- .../src/controller/init-controller.test.ts | 2 +- .../cli/src/controller/init-controller.ts | 35 +++++++++++++---- packages/cli/src/utils/build.ts | 7 +--- packages/cli/src/utils/utils.ts | 12 ++++++ packages/cli/tsconfig.json | 3 +- 7 files changed, 70 insertions(+), 31 deletions(-) diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 79bb32c9d2..1b1515c0b8 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -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) diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 3199ae028e..339e27f50a 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -128,8 +128,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({ @@ -147,17 +147,24 @@ export default class Init extends Command { project: ProjectSpecBase, projectPath: string, flags: {npm: boolean; 'install-dependencies': boolean} - ): Promise { - const [defaultEndpoint, defaultAuthor, defaultDescription] = await readDefaults(projectPath); - - 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); + ): Promise<{isMultiChainProject: boolean}> { + const { + author: defaultAuthor, + description: defaultDescription, + endpoint: defaultEndpoint, + isMultiChainProject, + } = await readDefaults(projectPath); + + if (!isMultiChainProject) { + 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); + } } const descriptionHint = defaultDescription.substring(0, 40).concat('...'); project.author = await input({message: 'Author', required: true, default: defaultAuthor}); @@ -170,14 +177,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 { diff --git a/packages/cli/src/controller/init-controller.test.ts b/packages/cli/src/controller/init-controller.test.ts index b0e0749c72..db796ec5ef 100644 --- a/packages/cli/src/controller/init-controller.test.ts +++ b/packages/cli/src/controller/init-controller.test.ts @@ -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); diff --git a/packages/cli/src/controller/init-controller.ts b/packages/cli/src/controller/init-controller.ts index 8c6178b542..cfab31c635 100644 --- a/packages/cli/src/controller/init-controller.ts +++ b/packages/cli/src/controller/init-controller.ts @@ -20,6 +20,7 @@ import { defaultEnvLocalPath, defaultEnvPath, defaultGitIgnorePath, + defaultMultiChainYamlManifestPath, defaultTSManifestPath, defaultYamlManifestPath, errorHandle, @@ -138,12 +139,21 @@ export async function cloneProjectTemplate( return projectPath; } -export async function readDefaults(projectPath: string): Promise { +export async function readDefaults(projectPath: string): Promise<{ + endpoint: string | string[]; + author: string; + description: string; + isMultiChainProject: boolean; +}> { const packageData = await fs.promises.readFile(`${projectPath}/package.json`); const currentPackage = JSON.parse(packageData.toString()); - let endpoint: ProjectNetworkConfig['endpoint']; + const author: string = currentPackage.author; + const description: string = currentPackage.description; + let endpoint: string[] | string; + 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'); @@ -152,23 +162,32 @@ export async function readDefaults(projectPath: string): Promise { }); 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; + endpoint = extractedYamlValues.network.endpoint as string[] | string; + } 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 { +export async function prepare( + projectPath: string, + project: ProjectSpecBase, + isMultiChainProject = false +): Promise { 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'); } diff --git a/packages/cli/src/utils/build.ts b/packages/cli/src/utils/build.ts index d49cb9b506..dfdcb93c99 100644 --- a/packages/cli/src/utils/build.ts +++ b/packages/cli/src/utils/build.ts @@ -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';` + @@ -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)); diff --git a/packages/cli/src/utils/utils.ts b/packages/cli/src/utils/utils.ts index dd1fbc2792..2d7fcb81a6 100644 --- a/packages/cli/src/utils/utils.ts +++ b/packages/cli/src/utils/utils.ts @@ -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'; @@ -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; +} diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index dabc104a0b..632972e860 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -8,7 +8,8 @@ "rootDir": "src", "tsBuildInfoFile": "lib/.tsbuildinfo", "outDir": "lib", - "noImplicitAny": true + "noImplicitAny": true, + "strict": true }, "references": [{"path": "../common"}, {"path": "../common-substrate"}, {"path": "../utils"}], "include": ["src/**/*"], From 034eaa30469075c3bb7a077d51410046a58bb548 Mon Sep 17 00:00:00 2001 From: yoozo Date: Mon, 18 Nov 2024 10:24:39 +0800 Subject: [PATCH 2/3] Update packages/cli/src/commands/init.ts Co-authored-by: Scott Twiname --- packages/cli/src/commands/init.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 339e27f50a..90898c6ea5 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -159,7 +159,7 @@ export default class Init extends Command { project.endpoint = !Array.isArray(defaultEndpoint) ? [defaultEndpoint] : defaultEndpoint; const userInput = await input({ message: 'RPC endpoint:', - default: defaultEndpoint[0] ?? 'wss://polkadot.api.onfinality.io/public-ws', + default: defaultEndpoint[0], required: false, }); if (!project.endpoint.includes(userInput)) { From 1c328c6e7412f7cc780cdce9158be8676101a27f Mon Sep 17 00:00:00 2001 From: Tate Date: Mon, 18 Nov 2024 03:05:50 +0000 Subject: [PATCH 3/3] Support init the endpoint type of ProjectNetworkConfig --- packages/cli/src/commands/init.ts | 21 +++++++++++++++---- .../cli/src/controller/init-controller.ts | 6 +++--- packages/cli/src/types.ts | 4 ++-- packages/cli/tsconfig.json | 3 +-- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 90898c6ea5..d88b6af1cc 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -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'; @@ -156,15 +157,17 @@ export default class Init extends Command { } = await readDefaults(projectPath); if (!isMultiChainProject) { - project.endpoint = !Array.isArray(defaultEndpoint) ? [defaultEndpoint] : defaultEndpoint; + const projectEndpoints: string[] = this.extractEndpoints(defaultEndpoint); const userInput = await input({ message: 'RPC endpoint:', - default: defaultEndpoint[0], + default: projectEndpoints[0], required: false, }); - if (!project.endpoint.includes(userInput)) { - (project.endpoint as string[]).push(userInput); + if (!projectEndpoints.includes(userInput)) { + projectEndpoints.push(userInput); } + + project.endpoint = projectEndpoints; } const descriptionHint = defaultDescription.substring(0, 40).concat('...'); project.author = await input({message: 'Author', required: true, default: defaultAuthor}); @@ -219,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); + } } diff --git a/packages/cli/src/controller/init-controller.ts b/packages/cli/src/controller/init-controller.ts index cfab31c635..7a8294e90f 100644 --- a/packages/cli/src/controller/init-controller.ts +++ b/packages/cli/src/controller/init-controller.ts @@ -140,7 +140,7 @@ export async function cloneProjectTemplate( } export async function readDefaults(projectPath: string): Promise<{ - endpoint: string | string[]; + endpoint: ProjectNetworkConfig['endpoint']; author: string; description: string; isMultiChainProject: boolean; @@ -149,7 +149,7 @@ export async function readDefaults(projectPath: string): Promise<{ const currentPackage = JSON.parse(packageData.toString()); const author: string = currentPackage.author; const description: string = currentPackage.description; - let endpoint: string[] | string; + let endpoint: ProjectNetworkConfig['endpoint']; let isMultiChainProject = false; const defaultTsPath = defaultTSManifestPath(projectPath); const defaultYamlPath = defaultYamlManifestPath(projectPath); @@ -165,7 +165,7 @@ export async function readDefaults(projectPath: string): Promise<{ } 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 as string[] | string; + endpoint = extractedYamlValues.network.endpoint; } else if (fs.existsSync(defaultMultiChainPath)) { endpoint = []; isMultiChainProject = true; diff --git a/packages/cli/src/types.ts b/packages/cli/src/types.ts index 23b96a2761..949b64dae6 100644 --- a/packages/cli/src/types.ts +++ b/packages/cli/src/types.ts @@ -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 { diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index 632972e860..dabc104a0b 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -8,8 +8,7 @@ "rootDir": "src", "tsBuildInfoFile": "lib/.tsbuildinfo", "outDir": "lib", - "noImplicitAny": true, - "strict": true + "noImplicitAny": true }, "references": [{"path": "../common"}, {"path": "../common-substrate"}, {"path": "../utils"}], "include": ["src/**/*"],