From d72f2ad0cac92718d66b0b92202c9483b5d3b050 Mon Sep 17 00:00:00 2001 From: bz888 Date: Fri, 20 Oct 2023 14:48:46 +1300 Subject: [PATCH] refactor ts/yaml loaders and extractors --- packages/cli/CHANGELOG.md | 2 +- packages/cli/src/commands/codegen/generate.ts | 99 +++++--- packages/cli/src/constants.ts | 1 + .../cli/src/controller/generate-controller.ts | 220 ++++++++++-------- .../cli/src/controller/init-controller.ts | 51 ++-- packages/cli/src/utils/utils.ts | 57 ++--- 6 files changed, 250 insertions(+), 180 deletions(-) diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index c018f2b2ea..1dd0133ff3 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -5,7 +5,7 @@ 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 +### Added - ts-manifest compatibility with `codegen:generate` command (#2111) ## [4.0.4] - 2023-10-17 diff --git a/packages/cli/src/commands/codegen/generate.ts b/packages/cli/src/commands/codegen/generate.ts index 0945b9031a..d5b9724e41 100644 --- a/packages/cli/src/commands/codegen/generate.ts +++ b/packages/cli/src/commands/codegen/generate.ts @@ -5,20 +5,22 @@ import fs, {lstatSync} from 'fs'; import path from 'path'; import {EventFragment, FunctionFragment} from '@ethersproject/abi/src.ts/fragments'; import {Command, Flags} from '@oclif/core'; -import {DEFAULT_MANIFEST, DEFAULT_TS_MANIFEST, extensionIsTs, getProjectRootAndManifest} from '@subql/common'; +import {DEFAULT_MANIFEST, DEFAULT_TS_MANIFEST, extensionIsTs} from '@subql/common'; import {SubqlRuntimeDatasource as EthereumDs} from '@subql/types-ethereum'; import {parseContractPath} from 'typechain'; -import {Document, Node} from 'yaml'; import { constructMethod, filterExistingMethods, filterObjectsByStateMutability, generateHandlers, - generateManifest, + generateManifestTs, + generateManifestYaml, getAbiInterface, getManifestData, prepareAbiDirectory, prepareInputFragments, + tsExtractor, + yamlExtractor, } from '../../controller/generate-controller'; import {extractFromTs} from '../../utils'; @@ -49,8 +51,7 @@ export default class Generate extends Command { async run(): Promise { const {flags} = await this.parse(Generate); const {abiPath, address, events, file, functions, startBlock} = flags; - let manifest: string; - let root: string; + let manifest: string, root: string; let isTs: boolean; const projectPath = path.resolve(file ?? process.cwd()); @@ -87,40 +88,68 @@ export default class Generate extends Command { const eventsFragments = abiInterface.events; const functionFragments = filterObjectsByStateMutability(abiInterface.functions); - let existingManifest: Document, true> | string; - let existingDs: EthereumDs[] | string; - // ts check if the file is ts - if (isTs) { - existingManifest = await fs.promises.readFile(manifest, 'utf8'); - - const extractedDatasources = extractFromTs(existingManifest, { - dataSources: undefined, - }); - existingDs = extractedDatasources.dataSources as string; - } else { - // yaml - existingManifest = await getManifestData(manifest); - existingDs = ((existingManifest.get('dataSources') as any)?.toJSON() as EthereumDs[]) ?? []; - } - const selectedEvents = await prepareInputFragments('event', events, eventsFragments, abiName); const selectedFunctions = await prepareInputFragments('function', functions, functionFragments, abiName); - const [cleanEvents, cleanFunctions] = filterExistingMethods(selectedEvents, selectedFunctions, existingDs, address); - - const constructedEvents: SelectedMethod[] = constructMethod(cleanEvents); - const constructedFunctions: SelectedMethod[] = constructMethod(cleanFunctions); + let cleanEvents: Record, + cleanFunctions: Record, + constructedEvents: SelectedMethod[], + constructedFunctions: SelectedMethod[]; try { - const userInput: UserInput = { - startBlock: startBlock, - functions: constructedFunctions, - events: constructedEvents, - abiPath: `./abis/${abiFileName}`, - address: address, - }; - - await generateManifest(manifest, userInput, existingManifest); + if (isTs) { + const existingManifest = await fs.promises.readFile(manifest, 'utf8'); + const extractedDatasources = extractFromTs(existingManifest, { + dataSources: undefined, + }); + const existingDs = extractedDatasources.dataSources as string; + const [cleanEvents, cleanFunctions] = filterExistingMethods( + selectedEvents, + selectedFunctions, + existingDs, + address, + tsExtractor + ); + + constructedEvents = constructMethod(cleanEvents); + constructedFunctions = constructMethod(cleanFunctions); + + const userInput: UserInput = { + startBlock: startBlock, + functions: constructedFunctions, + events: constructedEvents, + abiPath: `./abis/${abiFileName}`, + address: address, + }; + + await generateManifestTs(manifest, userInput, existingManifest); + } else { + // yaml + const existingManifest = await getManifestData(manifest); + const existingDs = ((existingManifest.get('dataSources') as any)?.toJSON() as EthereumDs[]) ?? []; + + [cleanEvents, cleanFunctions] = filterExistingMethods( + selectedEvents, + selectedFunctions, + existingDs, + address, + yamlExtractor + ); + + constructedEvents = constructMethod(cleanEvents); + constructedFunctions = constructMethod(cleanFunctions); + + const userInput: UserInput = { + startBlock: startBlock, + functions: constructedFunctions, + events: constructedEvents, + abiPath: `./abis/${abiFileName}`, + address: address, + }; + + await generateManifestYaml(manifest, userInput, existingManifest); + } + await generateHandlers([constructedEvents, constructedFunctions], root, abiName); this.log('-----------Generated-----------'); @@ -132,7 +161,7 @@ export default class Generate extends Command { }); this.log('-------------------------------'); } catch (e) { - throw new Error(e.message); + this.error(e); } } } diff --git a/packages/cli/src/constants.ts b/packages/cli/src/constants.ts index 539c3b05d1..f47c1dcb73 100644 --- a/packages/cli/src/constants.ts +++ b/packages/cli/src/constants.ts @@ -13,3 +13,4 @@ export const BASE_TEMPLATE_URl = 'https://templates.subquery.network'; // Regex for cold tsManifest export const ENDPOINT_REG = /endpoint:\s*(\[[^\]]+\]|['"`][^'"`]+['"`])/; +export const ADDRESS_REG = /address\s*:\s*['"]([^'"]+)['"]/; diff --git a/packages/cli/src/controller/generate-controller.ts b/packages/cli/src/controller/generate-controller.ts index 4950aa856a..a2c51920e6 100644 --- a/packages/cli/src/controller/generate-controller.ts +++ b/packages/cli/src/controller/generate-controller.ts @@ -19,6 +19,7 @@ import * as inquirer from 'inquirer'; import {upperFirst, difference, pickBy} from 'lodash'; import {Document, parseDocument, YAMLSeq} from 'yaml'; import {SelectedMethod, UserInput} from '../commands/codegen/generate'; +import {ADDRESS_REG} from '../constants'; import { extractArrayValueFromTsManifest, extractFromTs, @@ -132,14 +133,17 @@ export function generateHandlerName(name: string, abiName: string, type: 'tx' | return `handle${upperFirst(name)}${upperFirst(abiName)}${upperFirst(type)}`; } -export function constructDatasources(userInput: UserInput, isTs: boolean): EthereumDs | string { - const abiName = parseContractPath(userInput.abiPath).name; +function generateFormattedHandlers( + userInput: UserInput, + abiName: string, + kindModifier: (kind: string) => any +): SubqlRuntimeHandler[] { const formattedHandlers: SubqlRuntimeHandler[] = []; userInput.functions.forEach((fn) => { const handler: SubqlRuntimeHandler = { handler: generateHandlerName(fn.name, abiName, 'tx'), - kind: isTs ? 'EthereumHandlerKind.Call' : (EthereumHandlerKind.Call as any), + kind: kindModifier('EthereumHandlerKind.Call'), filter: { function: fn.method, }, @@ -150,7 +154,7 @@ export function constructDatasources(userInput: UserInput, isTs: boolean): Ether userInput.events.forEach((event) => { const handler: SubqlRuntimeHandler = { handler: generateHandlerName(event.name, abiName, 'log'), - kind: isTs ? 'EthereumHandlerKind.Event' : (EthereumHandlerKind.Event as any), + kind: kindModifier('EthereumHandlerKind.Event'), filter: { topics: [event.method], }, @@ -158,31 +162,42 @@ export function constructDatasources(userInput: UserInput, isTs: boolean): Ether formattedHandlers.push(handler); }); - const assets = new Map([[abiName, {file: userInput.abiPath}]]); + return formattedHandlers; +} - if (isTs) { - const handlersString = tsStringify(formattedHandlers as any); +export function constructDatasourcesTs(userInput: UserInput): string { + const abiName = parseContractPath(userInput.abiPath).name; + const formattedHandlers = generateFormattedHandlers(userInput, abiName, (kind) => kind); + const handlersString = tsStringify(formattedHandlers as any); + + return `{ + kind: EthereumDatasourceKind.Runtime, + startBlock: ${userInput.startBlock}, + options: { + abi: '${abiName}', + ${userInput.address && `address: '${userInput.address}',`} + }, + assets: new Map([['${abiName}', {file: '${userInput.abiPath}'}]]), + mapping: { + file: '${DEFAULT_HANDLER_BUILD_PATH}', + handlers: ${handlersString} + } + }`; +} + +export function constructDatasourcesYaml(userInput: UserInput): EthereumDs { + const abiName = parseContractPath(userInput.abiPath).name; + const formattedHandlers = generateFormattedHandlers(userInput, abiName, (kind) => { + if (kind === 'EthereumHandlerKind.Call') return EthereumHandlerKind.Call; + return EthereumHandlerKind.Event; + }); + const assets = new Map([[abiName, {file: userInput.abiPath}]]); - return `{ - kind: EthereumDatasourceKind.Runtime, - startBlock: ${userInput.startBlock}, - options: { - abi: '${abiName}', - ${userInput.address && `address: '${userInput.address}',`} - }, - assets: new Map([['${abiName}', {file: '${userInput.abiPath}'}]]), - mapping: { - file: '${DEFAULT_HANDLER_BUILD_PATH}', - handlers: ${handlersString} - } - }`; - } return { kind: EthereumDatasourceKind.Runtime, startBlock: userInput.startBlock, options: { abi: abiName, - // address: userInput.address, ...(userInput.address && {address: userInput.address}), }, assets: assets, @@ -246,58 +261,22 @@ function filterExistingFragments( return cleanFragments; } -export function filterExistingMethods( +type ManifestExtractor = ( + dataSources: T, + casedInputAddress: string | undefined +) => { + existingEvents: string[]; + existingFunctions: string[]; +}; + +export function filterExistingMethods( eventFragments: Record, functionFragments: Record, - dataSources: EthereumDs[] | string, - address: string | undefined + dataSources: T, + address: string | undefined, + extractor: ManifestExtractor ): [Record, Record] { - const existingEvents: string[] = []; - const existingFunctions: string[] = []; - - const casedInputAddress = address && address.toLowerCase(); - - if (typeof dataSources === 'string') { - const addressRegex = /address\s*:\s*['"]([^'"]+)['"]/; - splitArrayString(dataSources) - .filter((d) => { - const match = d.match(addressRegex); - return match && match[1].toLowerCase() === casedInputAddress; - }) - .forEach((d) => { - const topicsReg = /topics:\s*(\[[^\]]+\]|['"`][^'"`]+['"`])/; - const functionReg = /function\s*:\s*['"]([^'"]+)['"]/; - const z = extractArrayValueFromTsManifest(d, 'handlers'); - - const regResult = extractFromTs(z, { - topics: topicsReg, - function: functionReg, - }); - if (regResult.topics !== null) { - existingEvents.push(regResult.topics[0]); - } - if (regResult.function !== null) { - existingFunctions.push(regResult.function as string); - } - }); - } else { - dataSources - .filter((d: EthereumDs) => { - if (casedInputAddress && d.options.address) { - return casedInputAddress.toLowerCase() === d.options.address.toLowerCase(); - } - return casedInputAddress === d.options.address || (!casedInputAddress && !d.options.address); - }) - .forEach((handler: any) => { - if ('topics' in handler.filter) { - // topic[0] is the method - existingEvents.push((handler.filter as EthereumLogFilter).topics[0]); - } - if ('function' in handler.filter) { - existingFunctions.push((handler.filter as EthereumTransactionFilter).function); - } - }); - } + const {existingEvents, existingFunctions} = extractor(dataSources, address && address.toLowerCase()); return [ filterExistingFragments(eventFragments, existingEvents), @@ -305,6 +284,58 @@ export function filterExistingMethods( ]; } +export const yamlExtractor: ManifestExtractor = (dataSources, casedInputAddress) => { + const existingEvents: string[] = []; + const existingFunctions: string[] = []; + + dataSources + .filter((d: EthereumDs) => { + if (casedInputAddress && d.options.address) { + return casedInputAddress === d.options.address.toLowerCase(); + } + return casedInputAddress === d.options.address || (!casedInputAddress && !d.options.address); + }) + .forEach((handler: any) => { + if ('topics' in handler.filter) { + existingEvents.push((handler.filter as EthereumLogFilter).topics[0]); + } + if ('function' in handler.filter) { + existingFunctions.push((handler.filter as EthereumTransactionFilter).function); + } + }); + + return {existingEvents, existingFunctions}; +}; + +export const tsExtractor: ManifestExtractor = (dataSources, casedInputAddress) => { + const existingEvents: string[] = []; + const existingFunctions: string[] = []; + + splitArrayString(dataSources) + .filter((d) => { + const match = d.match(ADDRESS_REG); + return match && match[1].toLowerCase() === casedInputAddress; + }) + .forEach((d) => { + const topicsReg = /topics:\s*(\[[^\]]+\]|['"`][^'"`]+['"`])/; + const functionReg = /function\s*:\s*['"]([^'"]+)['"]/; + const extractedValue = extractFromTs(d, {handler: undefined}) as {handler: string}; + + const regResult = extractFromTs(extractedValue.handler, { + topics: topicsReg, + function: functionReg, + }); + if (regResult.topics !== null) { + existingEvents.push(regResult.topics[0]); + } + if (regResult.function !== null) { + existingFunctions.push(regResult.function as string); + } + }); + + return {existingEvents, existingFunctions}; +}; + export async function getManifestData(manifestPath: string): Promise { const existingManifest = await fs.promises.readFile(path.join(manifestPath), 'utf8'); return parseDocument(existingManifest); @@ -318,36 +349,35 @@ export function prependDatasources(dsStr: string, toPendStr: string): string { throw new Error('Input string is not a valid JSON array string'); } } -export async function generateManifest( +export async function generateManifestTs( manifestPath: string, userInput: UserInput, - existingManifestData: Document | string + existingManifestData: string ): Promise { - // if it is still using a yaml config - - let inputDs: EthereumDs | string; - - if (typeof existingManifestData !== 'string') { - inputDs = constructDatasources(userInput, false); - const dsNode = existingManifestData.get('dataSources') as YAMLSeq; - if (!dsNode || !dsNode.items.length) { - // To ensure output is in yaml format - const cleanDs = new YAMLSeq(); - cleanDs.add(inputDs); - existingManifestData.set('dataSources', cleanDs); - } else { - dsNode.add(inputDs); - } - await fs.promises.writeFile(path.join(manifestPath), existingManifestData.toString(), 'utf8'); - } else { - inputDs = constructDatasources(userInput, true); + const inputDs = constructDatasourcesTs(userInput); - const extractedDs = extractFromTs(existingManifestData, {dataSources: undefined}); - const v = prependDatasources(extractedDs.dataSources as string, inputDs as string); - const updateManifest = replaceArrayValueInTsManifest(existingManifestData, 'dataSources', v); + const extractedDs = extractFromTs(existingManifestData, {dataSources: undefined}) as {dataSources: string}; + const v = prependDatasources(extractedDs.dataSources, inputDs); + const updateManifest = replaceArrayValueInTsManifest(existingManifestData, 'dataSources', v); - await fs.promises.writeFile(manifestPath, updateManifest, 'utf8'); + await fs.promises.writeFile(manifestPath, updateManifest, 'utf8'); +} +export async function generateManifestYaml( + manifestPath: string, + userInput: UserInput, + existingManifestData: Document +): Promise { + const inputDs = constructDatasourcesYaml(userInput); + const dsNode = existingManifestData.get('dataSources') as YAMLSeq; + if (!dsNode || !dsNode.items.length) { + // To ensure output is in yaml format + const cleanDs = new YAMLSeq(); + cleanDs.add(inputDs); + existingManifestData.set('dataSources', cleanDs); + } else { + dsNode.add(inputDs); } + await fs.promises.writeFile(path.join(manifestPath), existingManifestData.toString(), 'utf8'); } export function constructHandlerProps(methods: [SelectedMethod[], SelectedMethod[]], abiName: string): AbiPropType { diff --git a/packages/cli/src/controller/init-controller.ts b/packages/cli/src/controller/init-controller.ts index 637a930c12..078973ab94 100644 --- a/packages/cli/src/controller/init-controller.ts +++ b/packages/cli/src/controller/init-controller.ts @@ -12,10 +12,12 @@ import axios from 'axios'; import {copySync} from 'fs-extra'; import rimraf from 'rimraf'; import git from 'simple-git'; -import {Document, ParsedNode, parseDocument, YAMLMap, YAMLSeq} from 'yaml'; +import {parseDocument, YAMLMap, YAMLSeq} from 'yaml'; import {BASE_TEMPLATE_URl, ENDPOINT_REG} from '../constants'; import {isProjectSpecV1_0_0, ProjectSpecBase} from '../types'; import { + defaultTSManifestPath, + defaultYamlManifestPath, errorHandle, extractFromTs, findReplace, @@ -147,15 +149,17 @@ export async function readDefaults(projectPath: string): Promise { const packageData = await fs.promises.readFile(`${projectPath}/package.json`); const currentPackage = JSON.parse(packageData.toString()); let endpoint: string | string[]; + const defaultTsPath = defaultTSManifestPath(projectPath); + const defaultYamlPath = defaultYamlManifestPath(projectPath); - if (fs.existsSync(path.join(projectPath, DEFAULT_TS_MANIFEST))) { - const tsManifest = await fs.promises.readFile(path.join(projectPath, DEFAULT_TS_MANIFEST), 'utf8'); + if (fs.existsSync(defaultTsPath)) { + const tsManifest = await fs.promises.readFile(defaultTsPath, 'utf8'); const extractedTsValues = extractFromTs(tsManifest.toString(), { endpoint: ENDPOINT_REG, }); endpoint = extractedTsValues.endpoint; } else { - const yamlManifest = await fs.promises.readFile(path.join(projectPath, DEFAULT_MANIFEST), 'utf8'); + const yamlManifest = await fs.promises.readFile(defaultYamlPath, 'utf8'); const extractedYamlValues = parseDocument(yamlManifest).toJS() as ProjectManifestV1_0_0; endpoint = extractedYamlValues.network.endpoint; } @@ -194,8 +198,8 @@ export async function preparePackage(projectPath: string, project: ProjectSpecBa export async function prepareManifest(projectPath: string, project: ProjectSpecBase): Promise { //load and write manifest(project.ts/project.yaml) - const tsPath = path.join(`${projectPath}`, DEFAULT_TS_MANIFEST); - const yamlPath = path.join(`${projectPath}`, DEFAULT_MANIFEST); + const tsPath = defaultTSManifestPath(projectPath); + const yamlPath = defaultYamlManifestPath(projectPath); let manifestData: string; const isTs = fs.existsSync(tsPath); @@ -244,24 +248,37 @@ export async function prepareProjectScaffold(projectPath: string): Promise // remove all existing abis & handler files await prepareDirPath(path.join(projectPath, 'abis/'), false); await prepareDirPath(path.join(projectPath, 'src/mappings/'), true); - let manifest: Document.Parsed | string; - if (fs.existsSync(path.join(projectPath, DEFAULT_MANIFEST))) { - // clean datasource - manifest = parseDocument(await fs.promises.readFile(path.join(projectPath, DEFAULT_MANIFEST), 'utf8')); - manifest.set('dataSources', new YAMLSeq()); - await fs.promises.writeFile(path.join(projectPath, DEFAULT_MANIFEST), manifest.toString(), 'utf8'); - } else { - // clean dataSources - manifest = await fs.promises.readFile(path.join(projectPath, DEFAULT_TS_MANIFEST), 'utf8'); - const updateManifest = replaceArrayValueInTsManifest(manifest, 'dataSources', '[]'); - await fs.promises.writeFile(path.join(projectPath, DEFAULT_TS_MANIFEST), updateManifest, 'utf8'); + const defaultTsPath = defaultTSManifestPath(projectPath); + + try { + // clean dataSources + if (fs.existsSync(defaultTsPath)) { + // ts check should be first + await prepareProjectScaffoldTS(defaultTsPath); + } else { + await prepareProjectScaffoldYAML(defaultYamlManifestPath(projectPath)); + } + } catch (e) { + throw new Error('Failed to prepare project scaffold'); } // remove handler file from index.ts fs.truncateSync(path.join(projectPath, 'src/index.ts'), 0); } +async function prepareProjectScaffoldTS(defaultTsPath: string): Promise { + const manifest = await fs.promises.readFile(defaultTsPath, 'utf8'); + const updateManifest = replaceArrayValueInTsManifest(manifest, 'dataSources', '[]'); + await fs.promises.writeFile(defaultTsPath, updateManifest, 'utf8'); +} + +async function prepareProjectScaffoldYAML(defaultYamlPath: string): Promise { + const manifest = parseDocument(await fs.promises.readFile(defaultYamlPath, 'utf8')); + manifest.set('dataSources', new YAMLSeq()); + await fs.promises.writeFile(defaultYamlPath, manifest.toString(), 'utf8'); +} + export async function validateEthereumProjectManifest(projectPath: string): Promise { let manifest: any; const isTs = fs.existsSync(path.join(projectPath, DEFAULT_TS_MANIFEST)); diff --git a/packages/cli/src/utils/utils.ts b/packages/cli/src/utils/utils.ts index 1dcf2552cf..2d7fc9c08b 100644 --- a/packages/cli/src/utils/utils.ts +++ b/packages/cli/src/utils/utils.ts @@ -5,6 +5,7 @@ import fs, {existsSync, readFileSync} from 'fs'; import os from 'os'; import path from 'path'; import {promisify} from 'util'; +import {DEFAULT_MANIFEST, DEFAULT_TS_MANIFEST} from '@subql/common'; import {SubqlRuntimeHandler} from '@subql/common-ethereum'; import axios from 'axios'; import cli, {ux} from 'cli-ux'; @@ -114,12 +115,15 @@ export function findReplace(manifest: string, replacer: RegExp, value: string): return manifest.replace(replacer, value); } -export function replaceArrayValueInTsManifest(manifest: string, key: string, newValue: string): string { +export function findArrayIndicesTsManifest(manifest: string, key: string): [number, number] { + // JavaScript's regex engine does not support recursive patterns like (?1). + // This regex would work in engines that support recursion, such as PCRE (Perl-Compatible Regular Expressions). + const start = manifest.indexOf(`${key}:`); - if (start === -1) return manifest; // If the value is not found, return the original manifest + if (start === -1) throw new Error(`${key} not found`); let openBrackets = 0; - let startIndex, endIndex; + let startIndex: number, endIndex: number; for (let i = start; i < manifest.length; i++) { if (manifest[i] === '[') { @@ -134,36 +138,16 @@ export function replaceArrayValueInTsManifest(manifest: string, key: string, new } } - if (openBrackets !== 0) return manifest; // Unbalanced brackets, return the original manifest + if (openBrackets !== 0) throw new Error(`${key} contains unbalanced brackets`); - // Replace the old array with the new value + return [startIndex, endIndex]; +} +export function replaceArrayValueInTsManifest(manifest: string, key: string, newValue: string): string { + const [startIndex, endIndex] = findArrayIndicesTsManifest(manifest, key); return manifest.slice(0, startIndex) + newValue + manifest.slice(endIndex + 1); } - -// JavaScript's regex engine does not support recursive patterns like (?1). -// This regex would work in engines that support recursion, such as PCRE (Perl-Compatible Regular Expressions). -export function extractArrayValueFromTsManifest(manifest: string, value: string): string | null { - const start = manifest.indexOf(`${value}:`); - if (start === -1) return null; - - let openBrackets = 0; - let startIndex, endIndex; - - for (let i = start; i < manifest.length; i++) { - if (manifest[i] === '[') { - if (openBrackets === 0) startIndex = i; - openBrackets++; - } else if (manifest[i] === ']') { - openBrackets--; - if (openBrackets === 0) { - endIndex = i; - break; - } - } - } - - if (openBrackets !== 0) return null; // Unbalanced brackets - +export function extractArrayValueFromTsManifest(manifest: string, key: string): string | null { + const [startIndex, endIndex] = findArrayIndicesTsManifest(manifest, key); return manifest.slice(startIndex, endIndex + 1); } @@ -197,8 +181,9 @@ export function extractFromTs( ): {[key: string]: string | string[] | null} { const result: {[key: string]: string | string[] | null} = {}; const arrKeys = ['endpoint', 'topics']; + const nestArr = ['dataSources', 'handlers']; for (const key in patterns) { - if (key !== 'dataSources') { + if (!nestArr.includes(key)) { const match = manifest.match(patterns[key]); if (arrKeys.includes(key) && match) { @@ -209,7 +194,7 @@ export function extractFromTs( result[key] = match ? match[1] : null; } } else { - result[key] = extractArrayValueFromTsManifest(manifest, 'dataSources'); + result[key] = extractArrayValueFromTsManifest(manifest, key); } } @@ -245,3 +230,11 @@ export function validateEthereumTsManifest(manifest: string): boolean { const nodePattern = /@subql\/node-ethereum/; return !!typePattern.test(manifest) && !!nodePattern.test(manifest); } + +export function defaultYamlManifestPath(projectPath: string): string { + return path.join(projectPath, DEFAULT_MANIFEST); +} + +export function defaultTSManifestPath(projectPath: string): string { + return path.join(projectPath, DEFAULT_TS_MANIFEST); +}