Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix generated code not building when conflicing names #216

Merged
merged 3 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/common-cosmos/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ 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
- Codegen generating types with duplicate names leading to invalid TS (#216)

## [3.2.1] - 2023-11-08
### Fixed
Expand Down
69 changes: 64 additions & 5 deletions packages/common-cosmos/src/codegen/codegen-controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,28 @@
{
'osmosis.gamm.v1beta1': {
file: './proto/osmosis/gamm/v1beta1/tx.proto',

messages: ['MsgSwapExactAmountIn'],
},
},
{
'osmosis.poolmanager.v1beta1': {
file: './proto/osmosis/poolmanager/v1beta1/swap_route.proto',
messages: ['SwapAmountInRoute'],
messages: ['MsgSwapAmountInRoute'],
},
},
];
expect(prepareProtobufRenderProps(mockChainTypes, PROJECT_PATH)).toStrictEqual([
{
messageNames: ['MsgSwapExactAmountIn'],
namespace: 'osmosis.gamm.v1beta1.tx',
name: 'OsmosisGammV1beta1Tx',
path: './proto-interfaces/osmosis/gamm/v1beta1/tx',
},
{
messageNames: ['SwapAmountInRoute'],
messageNames: ['MsgSwapAmountInRoute'],
namespace: 'osmosis.poolmanager.v1beta1.swap_route',
name: 'OsmosisPoolmanagerV1beta1Swap_route',
path: './proto-interfaces/osmosis/poolmanager/v1beta1/swap_route',
},
]);
Expand All @@ -61,14 +66,16 @@
{
'osmosis.poolmanager.v1beta1': {
file: './proto/osmosis/poolmanager/v1beta1/swap_route.proto',
messages: ['SwapAmountInRoute'],
messages: ['MsgSwapAmountInRoute'],
},
},
undefined,
];
expect(prepareProtobufRenderProps(mixedMockChainTypes, PROJECT_PATH)).toStrictEqual([
{
messageNames: ['SwapAmountInRoute'],
messageNames: ['MsgSwapAmountInRoute'],
name: 'OsmosisPoolmanagerV1beta1Swap_route',
namespace: 'osmosis.poolmanager.v1beta1.swap_route',
path: './proto-interfaces/osmosis/poolmanager/v1beta1/swap_route',
},
]);
Expand Down Expand Up @@ -104,6 +111,58 @@
expect(v.toString()).toBe('fake proto');
await promisify(rimraf)(tp);
});

it('renders correct codegen from ejs', async () => {
const job = {
props: {
proto: [
{
messageNames: ['MsgSwapExactAmountIn'],
namespace: 'osmosis.gamm.v1beta1.tx',
name: 'OsmosisGammV1beta1Tx',
path: './proto-interfaces/osmosis/gamm/v1beta1/tx',
},
{
messageNames: ['SwapAmountInRoute'],
namespace: 'osmosis.poolmanager.v1beta1.swap_route',
name: 'OsmosisPoolmanagerV1beta1Swap_route',
path: './proto-interfaces/osmosis/poolmanager/v1beta1/swap_route',
},
],
},
helper: {upperFirst},
};

const output = await ejs.renderFile(path.resolve(__dirname, '../../templates/proto-interface.ts.ejs'), job);

// await fs.promises.writeFile(path.join(PROJECT_PATH, 'test.ts'), data);
const expectCodegen = `// SPDX-License-Identifier: Apache-2.0

// Auto-generated , DO NOT EDIT
import {CosmosMessage} from "@subql/types-cosmos";

import * as OsmosisGammV1beta1Tx from "./proto-interfaces/osmosis/gamm/v1beta1/tx";

import * as OsmosisPoolmanagerV1beta1Swap_route from "./proto-interfaces/osmosis/poolmanager/v1beta1/swap_route";


export namespace osmosis.gamm.v1beta1.tx {

export type MsgSwapExactAmountInMessage = CosmosMessage<OsmosisGammV1beta1Tx.MsgSwapExactAmountIn>;
}

export namespace osmosis.poolmanager.v1beta1.swap_route {

export type SwapAmountInRouteMessage = CosmosMessage<OsmosisPoolmanagerV1beta1Swap_route.SwapAmountInRoute>;
}

`;

expect(output).toEqual(expectCodegen);
// const output = await fs.promises.readFile(path.join(PROJECT_PATH, 'test.ts'));
// expect(output.toString()).toMatch(expectCodegen);
// await promisify(rimraf)(path.join(PROJECT_PATH, 'test.ts'));
});
});

describe('CosmWasm codegen', () => {
Expand Down Expand Up @@ -144,7 +203,7 @@
},
],
},
} as any;

Check warning on line 206 in packages/common-cosmos/src/codegen/codegen-controller.spec.ts

View workflow job for this annotation

GitHub Actions / code-style

Unexpected any. Specify a different type

expect(prepareSortedAssets([notCosmosDs], PROJECT_PATH)).toStrictEqual({});
});
Expand All @@ -162,7 +221,7 @@
},
]);
});
it('render correct codegen from ejs', async () => {
it('renders correct codegen from ejs', async () => {
const mockJob = {
contract: 'Cw20',
messages: {
Expand Down
75 changes: 58 additions & 17 deletions packages/common-cosmos/src/codegen/codegen-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import {CosmosChaintypes, CustomModule, CosmosRuntimeDatasource} from '@subql/types-cosmos';
import {Data} from 'ejs';
import {copySync} from 'fs-extra';
import {upperFirst} from 'lodash';
import {IDLObject} from 'wasm-ast-types';
import {isRuntimeCosmosDs} from '../project';
import {COSMWASM_OPTS, TELESCOPE_OPTS} from './constants';
Expand All @@ -26,6 +27,18 @@
const COSMWASM_INTERFACE_TEMPLATE_PATH = path.resolve(__dirname, '../../templates/cosmwasm-interfaces.ts.ejs');

interface ProtobufRenderProps {
/**
* The dot notation format of the path without PROTO dir
* @exmaple
* 'cosmos.auth.v1beta1.tx'
* */
namespace: string;
/**
* The camel case format of the path without PROTO dir
* @example
* 'CosmosAuthV1Beta1Tx'
* */
name: string;
messageNames: string[]; // all messages
path: string; // should process the file Path and concat with PROTO dir
}
Expand All @@ -42,6 +55,20 @@
return `./proto-interfaces/${path.replace(/^\.\/proto\/|\.proto$/g, '').replace(/\./g, '/')}`;
}

function pathToNamespace(path: string): string {
return path
.replace(/^\.\/proto\/|\.proto$/g, '')
.split(/(?<=\\\\)\/|(?<!\\)\//)
.join('.');
}

function pathToName(path: string): string {
return pathToNamespace(path)
.split('.')
.map((p) => upperFirst(p))
.join('');
}

export function isProtoPath(filePath: string, projectPath: string): boolean {
// check if the protobuf files are under ./proto directory
return !!path.join(projectPath, filePath).startsWith(path.join(projectPath, './proto/'));
Expand Down Expand Up @@ -151,24 +178,36 @@
return [];
}
return chaintypes.filter(Boolean).flatMap((chaintype) => {
return Object.entries(chaintype).map(([key, value]) => {
const filePath = path.join(projectPath, value.file);
if (!fs.existsSync(filePath)) {
throw new Error(`Error: chainType ${key}, file ${value.file} does not exist`);
}
if (!isProtoPath(value.file, projectPath)) {
console.error(
`Codegen will not apply for this file: ${value.file} Please ensure it is under the ./proto directory if you want to run codegen on it`
);
}
return {
messageNames: value.messages,
path: processProtoFilePath(value.file),
};
});
return Object.entries(chaintype)
.map(([key, value]) => {
const filePath = path.join(projectPath, value.file);
if (!fs.existsSync(filePath)) {
throw new Error(`Error: chainType ${key}, file ${value.file} does not exist`);
}
if (!isProtoPath(value.file, projectPath)) {
console.error(
`Codegen will not apply for this file: ${value.file} Please ensure it is under the ./proto directory if you want to run codegen on it`
);
}

// We only need to generate for RPC messages that are always prefixed with Msg
const messages = value.messages.filter((m: string) => m.indexOf('Msg') === 0);
if (!messages.length) return;

return {
messageNames: messages,
namespace: pathToNamespace(value.file),
name: pathToName(value.file),
path: processProtoFilePath(value.file),
};
})
.filter(Boolean);
});
}

/**
* Makes a temporaray directory and populates it with some core protobufs used by all projects, then copies over the projects protobufs
* */
export async function tempProtoDir(projectPath: string): Promise<string> {
const tmpDir = await makeTempDir();
const userProto = path.join(projectPath, './proto');
Expand Down Expand Up @@ -198,11 +237,12 @@
prepareDirPath: (path: string, recreate: boolean) => Promise<void>,
renderTemplate: (templatePath: string, outputPath: string, templateData: Data) => Promise<void>,
upperFirst: (string?: string) => string,
mkdirProto: (projectPath: string) => Promise<string>
/** @deprecated */
mkdirProto?: (projectPath: string) => Promise<string>

Check warning on line 241 in packages/common-cosmos/src/codegen/codegen-controller.ts

View workflow job for this annotation

GitHub Actions / code-style

'mkdirProto' is defined but never used
): Promise<void> {
let tmpPath: string;
try {
tmpPath = await mkdirProto(projectPath);
tmpPath = await tempProtoDir(projectPath);
const protobufRenderProps = prepareProtobufRenderProps(chaintypes, projectPath);
const outputPath = path.join(projectPath, PROTO_INTERFACES_ROOT_DIR);
await prepareDirPath(path.join(projectPath, PROTO_INTERFACES_ROOT_DIR), true);
Expand All @@ -223,10 +263,11 @@
}
);
console.log('* Cosmos message wrappers generated !');
} catch (e: any) {

Check warning on line 266 in packages/common-cosmos/src/codegen/codegen-controller.ts

View workflow job for this annotation

GitHub Actions / code-style

Unexpected any. Specify a different type
const errorMessage = e.message.startsWith('Dependency')
? `Please add the missing protobuf file to ./proto directory`
: '';
console.log('ERRROR', e);
throw new Error(`Failed to generate from protobufs. ${e.message}, ${errorMessage}`);
} finally {
fs.rmSync(tmpPath, {recursive: true, force: true});
Expand Down
11 changes: 7 additions & 4 deletions packages/common-cosmos/templates/proto-interface.ts.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
// Auto-generated , DO NOT EDIT
import {CosmosMessage} from "@subql/types-cosmos";
<% props.proto.forEach(function(proto) { %>
import {<% proto.messageNames.forEach(function(msg, index) { %><%= helper.upperFirst(msg) %><% if (index < proto.messageNames.length - 1) { %>,<% } %><% }); %>} from "<%= proto.path %>";
import * as <%= proto.name %> from "<%= proto.path %>";
<% }); %>
<% props.proto.forEach(function(proto) { %>
export namespace <%= proto.namespace %> {
<% proto.messageNames.forEach(function(msg) { %>
export type <%= helper.upperFirst(msg) %>Message = CosmosMessage<<%= proto.name %>.<%= helper.upperFirst(msg) %>>;<% }); %>
}
<% }); %>
<% props.proto.forEach(function(proto) { %><% proto.messageNames.forEach(function(msg) { %>
export type <%= helper.upperFirst(msg) %>Message = CosmosMessage<<%= helper.upperFirst(msg) %>>;<% }); %>
<% }); %>
Loading