Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
4 changes: 2 additions & 2 deletions codegenerator/cli/CommandLineHelp.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ Initialize an indexer with one of the initialization options
* `-n`, `--name <NAME>` — The name of your project
* `-l`, `--language <LANGUAGE>` — The language used to write handlers

Possible values: `javascript`, `typescript`, `rescript`
Possible values: `typescript`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to keep ReScript option for contract import. No need to have a promt for it, only as a cli.


* `--api-token <API_TOKEN>` — The hypersync API key to be initialized in your templates .env file

Expand Down Expand Up @@ -142,7 +142,7 @@ Initialize Evm indexer from an example template

* `-t`, `--template <TEMPLATE>` — Name of the template to be used in initialization

Possible values: `greeter`, `erc20`
Possible values: `greeter`, `erc20`, `topic-filtering`, `wildcard-indexing`, `on-block-handler`, `effects-api`, `factory-indexer`, `multichain-indexer`



Expand Down
20 changes: 14 additions & 6 deletions codegenerator/cli/src/cli_args/init_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,22 @@ pub mod evm {

#[derive(Clone, Debug, ValueEnum, Serialize, Deserialize, EnumIter, EnumString, Display)]
pub enum Template {
#[strum(serialize = "Greeter")]
Greeter,
#[strum(serialize = "ERC20")]
Erc20,
#[strum(serialize = "Feature: Topic Filtering")]
TopicFiltering,
#[strum(serialize = "Feature: Wildcard Indexing")]
WildcardIndexing,
#[strum(serialize = "Feature: onBlock Handler")]
OnBlockHandler,
Comment on lines +31 to +40

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Template naming/serialization may drift across clap/strum/serde. Verify UX strings and parsing.

  • strum(serialize = "...") sets Display/EnumString, but clap::ValueEnum uses its own naming rules unless given explicit clap attributes; serde derives won’t use strum Display either.
  • Result: interactive/help text and config (de)serialization may show different strings than the ones you intend (“Feature: ...”).

Please confirm:

  • CLI shows the intended labels for these variants.
  • Non-interactive flags parse both human labels and machine labels as expected.
  • The new templates (effects-api, factory-indexer, multichain-indexer) are exposed to users (either here or via template_dirs). If they’re intentionally not in this enum, ensure template_dirs.rs maps them correctly.

You can run:


🏁 Script executed:

#!/bin/bash
# Inspect enum variants and any template mappings
rg -nP 'enum\s+Template\b' -C2 codegenerator/cli
rg -nP 'supported_languages\s*\(' codegenerator/cli
rg -nP '(effects_api_template|factory_indexer_template|multichain_indexer_template)' -n codegenerator/cli

Length of output: 1647


I need to gather more information about the Template enums and how they're used.<!-- [scratchpad_update]
[observations_additional]

  • Web search confirms: clap::ValueEnum has its own naming logic separate from strum serialize
  • clap uses kebab-case by default; can be overridden with #[clap(rename_all = "...")] attribute
  • strum(serialize = "...") controls Display/EnumString/serde serialization, NOT clap's ValueEnum parsing
  • When clap::ValueEnum and strum both present, clap won't automatically respect strum(serialize) unless #[clap(...)] attributes are explicitly added
  • This is exactly what the review comment warns about
  • ValueEnum.to_possible_value() is the method that controls CLI value parsing

[pending_critical]

  • Need to inspect the actual Template enum definition to see if clap attributes are present
  • Need to find where the new templates are defined/used
  • Need to check template_dirs.rs for template registration
    [scratchpad_end] -->

Need to inspect the actual Template enum and template registration in more detail.


🏁 Script executed:

#!/bin/bash
# Get full context of the Template enum at line 30
sed -n '28,80p' codegenerator/cli/src/cli_args/init_config.rs

Length of output: 2220


🏁 Script executed:

#!/bin/bash
# Get full context of the second Template enum at line 231
sed -n '229,240p' codegenerator/cli/src/cli_args/init_config.rs

Length of output: 285


🏁 Script executed:

#!/bin/bash
# Look for clap attributes on Template enum
rg -nP '#\[clap' codegenerator/cli/src/cli_args/init_config.rs -A2 -B2

Length of output: 243


🏁 Script executed:

#!/bin/bash
# Search for new template names in template_dirs.rs
rg -nP '(effects|factory|multichain)' codegenerator/cli/src/template_dirs.rs -i -C2

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check if there's a match on Template enum and ValueEnum derive to understand how it's used
ast-grep --pattern $'#[derive($$$ValueEnum$$$)]
pub enum Template {
  $$$
}'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for clap rename_all on the enum itself
rg -nP 'enum Template' codegenerator/cli -B5 | head -40

Length of output: 1303


🏁 Script executed:

#!/bin/bash
# Search in template_dirs for TemplateProvider or template registration logic
rg -nP 'fn.*template' codegenerator/cli/src/template_dirs.rs -i -n

Length of output: 1251


🏁 Script executed:

#!/bin/bash
# Look for where Template enum variants are used in value parsing
rg -nP 'to_possible_value|value_variants|Template::' codegenerator/cli/src/cli_args/init_config.rs -n

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check if there's any test or mock for template selection
fd -e rs codegenerator/cli -path '*/test*' -o -name '*test*' | xargs rg -l Template 2>/dev/null | head -5

Length of output: 360


🏁 Script executed:

#!/bin/bash
# Look for explicit use of strum in imports or dependencies
rg -n '^use.*strum' codegenerator/cli/src/

Length of output: 991


🏁 Script executed:

#!/bin/bash
# Look for where Template is actually parsed/matched in the interactive flow
rg -nP 'Template::|template.*select|select.*template' codegenerator/cli/src/cli_args/interactive_init/ -i

Length of output: 932


🏁 Script executed:

#!/bin/bash
# Check Cargo.toml for versions of clap, strum, serde
rg -n '(clap|strum|serde)' codegenerator/cli/Cargo.toml -A1

Length of output: 379


🏁 Script executed:

#!/bin/bash
# Look at the full enum definition including derive macros
sed -n '25,100p' codegenerator/cli/src/cli_args/init_config.rs

Length of output: 3034


I need to verify a few more critical details about how Template enum is used in argument parsing and whether clap attributes exist at the module level.


🏁 Script executed:

#!/bin/bash
# Check if Template is used in clap argument definitions
rg -nP 'template|Template' codegenerator/cli/src/cli_args/clap_definitions.rs -i -C3

Length of output: 1763


🏁 Script executed:

#!/bin/bash
# Look at the evm module declaration to see if there are clap attributes
sed -n '1,50p' codegenerator/cli/src/cli_args/init_config.rs

Length of output: 1537


🏁 Script executed:

#!/bin/bash
# Check how the Template is rendered in the interactive prompt (Display impl check)
rg -nP 'shared_prompts|SelectItem' codegenerator/cli/src/cli_args/interactive_init/shared_prompts.rs -C5

Length of output: 887


🏁 Script executed:

#!/bin/bash
# Look for where template names are actually output to user (help text, prompts)
rg -nP 'which.*template|select.*template' codegenerator/cli/src/cli_args/interactive_init/ -i -C3

Length of output: 1539


🏁 Script executed:

#!/bin/bash
# Check TemplateType enum to understand template registration
sed -n '179,220p' codegenerator/cli/src/template_dirs.rs

Length of output: 1422


🏁 Script executed:

#!/bin/bash
# Look for any rename_all or clap rename directives on evm module
grep -n 'mod evm' codegenerator/cli/src/cli_args/init_config.rs -B5 -A10

Length of output: 411


🏁 Script executed:

#!/bin/bash
# Search for ValueEnum implementation to see if to_possible_value is customized
rg -nP 'impl.*ValueEnum|to_possible_value' codegenerator/cli/src/cli_args/init_config.rs -n

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check template_dirs.rs TemplateType for variant names
sed -n '179,200p' codegenerator/cli/src/template_dirs.rs

Length of output: 679


Add #[clap(...)] attributes to Template enum variants to align clap parsing with strum Display strings.

The review concern is valid. Template enum (line 29-40) derives ValueEnum without explicit clap attributes, causing a mismatch:

  • Interactive UI displays strum serialized strings: "ERC20", "Feature: Topic Filtering"
  • clap parses using variant names as kebab-case: "erc-20", "topic-filtering"

This breaks non-interactive flag parsing (e.g., --template erc-20 would fail).

Fix: Add #[clap(name = "...")] attributes on each Template variant, mirroring the strum serialized values. Reference: Language enum (lines 346-352) demonstrates the correct pattern with explicit clap name attributes.

Regarding new templates: effects_api_template, factory_indexer_template, multichain_indexer_template are not found in the Template enum or template_dirs.rs. Verify whether these should be added to the enum or are intentionally excluded.

🤖 Prompt for AI Agents
In codegenerator/cli/src/cli_args/init_config.rs around lines 31 to 40, the
Template enum variants derive ValueEnum but lack clap name attributes so clap
parsing uses kebab-case variant names (e.g., "erc-20") while the interactive UI
uses strum serialized strings (e.g., "ERC20"); add #[clap(name = "...")] to each
variant with the exact string used in #[strum(serialize = "...")] (e.g.,
#[clap(name = "ERC20")]) so non-interactive flags parse the same values, and
also verify whether effects_api_template, factory_indexer_template, and
multichain_indexer_template should be added to this enum or intentionally
omitted and update template_dirs.rs accordingly.

#[strum(serialize = "Feature: Effects API")]
EffectsAPI,
#[strum(serialize = "Feature: Factory Indexer")]
FactoryIndexer,
#[strum(serialize = "Feature: Multichain Indexer")]
MultichainIndexer,
Comment on lines +35 to +46

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[strum(serialize = "Feature: Topic Filtering")]
TopicFiltering,
#[strum(serialize = "Feature: Wildcard Indexing")]
WildcardIndexing,
#[strum(serialize = "Feature: onBlock Handler")]
OnBlockHandler,
#[strum(serialize = "Feature: Effects API")]
EffectsAPI,
#[strum(serialize = "Feature: Factory Indexer")]
FactoryIndexer,
#[strum(serialize = "Feature: Multichain Indexer")]
MultichainIndexer,
#[strum(serialize = "Feature: Topic Filtering")]
TopicFiltering,
#[strum(serialize = "Feature: Wildcard Indexing")]
WildcardIndexing,
#[strum(serialize = "Feature: Block Handler")]
OnBlockHandler,
#[strum(serialize = "Feature: External Calls")]
EffectsAPI,
#[strum(serialize = "Feature: Factory Contract")]
FactoryIndexer,
#[strum(serialize = "Feature: Multichain Indexer")]
MultichainIndexer,

}

///A an object that holds all the values a user can select during
Expand Down Expand Up @@ -335,21 +349,15 @@ impl Ecosystem {
)]
///Which language do you want to write in?
pub enum Language {
#[clap(name = "javascript")]
JavaScript,
#[clap(name = "typescript")]
TypeScript,
#[clap(name = "rescript")]
ReScript,
}

impl Language {
// Logic to get the event handler directory based on the language
pub fn get_event_handler_directory(&self) -> String {
match self {
Language::ReScript => "./src/EventHandlers.res.js".to_string(),
Language::TypeScript => "src/EventHandlers.ts".to_string(),
Language::JavaScript => "./src/EventHandlers.js".to_string(),
}
}
}
Expand Down
22 changes: 4 additions & 18 deletions codegenerator/cli/src/cli_args/interactive_init/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ use crate::{
use anyhow::{Context, Result};
use inquire::{Select, Text};
use shared_prompts::prompt_template;
use std::str::FromStr;
use strum;
use strum::{Display, EnumIter, IntoEnumIterator};
use validation::{
Expand All @@ -30,7 +29,9 @@ enum EcosystemOption {
Fuel,
}

async fn prompt_ecosystem(cli_init_flow: Option<InitFlow>) -> Result<Ecosystem> {
async fn prompt_ecosystem(
cli_init_flow: Option<InitFlow>,
) -> Result<Ecosystem> {
let init_flow = match cli_init_flow {
Some(v) => v,
None => {
Expand Down Expand Up @@ -138,22 +139,7 @@ pub async fn prompt_missing_init_args(
}
};

let language = match init_args.language {
Some(args_language) => args_language,
None => {
let options = Language::iter()
.map(|language| language.to_string())
.collect::<Vec<String>>();

let input_language = Select::new("Which language would you like to use?", options)
.with_starting_cursor(1)
.prompt()
.context("prompting user to select language")?;

Language::from_str(&input_language)
.context("parsing user input for language selection")?
}
};
let language = Language::TypeScript;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd do it this way. Also need to perform this check only for contract import flow. Since templates are TS only.

Suggested change
let language = Language::TypeScript;
let language = match init_args.language {
Some(args_language) => args_language,
None => Language::TypeScript
};


let ecosystem = prompt_ecosystem(init_args.init_commands)
.await
Expand Down
16 changes: 4 additions & 12 deletions codegenerator/cli/src/config_parsing/graph_migration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,7 @@ pub struct BlockHandler {
// Logic to get the event handler directory based on the language
fn get_event_handler_directory(language: &Language) -> String {
match language {
Language::ReScript => "./src/EventHandlers.res.js".to_string(),
Language::TypeScript => "src/EventHandlers.ts".to_string(),
Language::JavaScript => "./src/EventHandlers.js".to_string(),
}
}

Expand Down Expand Up @@ -462,7 +460,7 @@ mod test {
let temp_dir = TempDir::new("temp_graph_migration_folder").unwrap();
// subgraph ID of USDC on Ethereum mainnet
let cid: &str = "QmU5V3jy56KnFbxX2uZagvMwocYZASzy1inX828W2XWtTd";
let language: Language = Language::ReScript;
let language: Language = Language::TypeScript;
let project_root = PathBuf::from(temp_dir.path());
super::generate_config_from_subgraph_id(&project_root, cid, &language)
.await
Expand Down Expand Up @@ -552,15 +550,9 @@ mod test {
// Unit test to check that the correct event handler directory is returned based on the language
#[test]
fn test_get_event_handler_directory() {
let language_1: Language = Language::ReScript;
let language_2: Language = Language::JavaScript;
let language_3: Language = Language::TypeScript;
let event_handler_directory_1 = super::get_event_handler_directory(&language_1);
let event_handler_directory_2 = super::get_event_handler_directory(&language_2);
let event_handler_directory_3 = super::get_event_handler_directory(&language_3);
assert_eq!(event_handler_directory_1, "./src/EventHandlers.res.js");
assert_eq!(event_handler_directory_2, "./src/EventHandlers.js");
assert_eq!(event_handler_directory_3, "src/EventHandlers.ts");
let language: Language = Language::TypeScript;
let event_handler_directory = super::get_event_handler_directory(&language);
assert_eq!(event_handler_directory, "src/EventHandlers.ts");
}
// Unit test to check that the correct network contract hashmap is generated
#[tokio::test]
Expand Down
11 changes: 2 additions & 9 deletions codegenerator/cli/src/executor/init.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
cli_args::{
clap_definitions::{InitArgs, ProjectPaths},
init_config::{self, Ecosystem, Language},
init_config::{self, Ecosystem},
interactive_init::prompt_missing_init_args,
},
commands,
Expand All @@ -19,7 +19,7 @@ use crate::{
template_dirs::TemplateDirs,
utils::file_system,
};
use anyhow::{anyhow, Context, Result};
use anyhow::{Context, Result};

use std::path::PathBuf;

Expand Down Expand Up @@ -258,13 +258,6 @@ pub async fn run_init_args(init_args: InitArgs, project_paths: &ProjectPaths) ->

commands::codegen::run_codegen(&config).await?;

if init_config.language == Language::ReScript {
let res_build_exit = commands::rescript::build(&parsed_project_paths.project_root).await?;
if !res_build_exit.success() {
return Err(anyhow!("Failed to build rescript"))?;
}
}

// If the project directory is not the current directory, print a message for user to cd into it
if parsed_project_paths.project_root != PathBuf::from(".") {
println!(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,7 @@ impl Event {
/// Returns the code to get the entity id from an event
pub fn get_entity_id_code(is_fuel: bool, language: &Language) -> String {
let int_as_string = |int_var_name| match language {
Language::ReScript => format!("{int_var_name}->Belt.Int.toString"),
Language::TypeScript | Language::JavaScript => int_var_name,
Language::TypeScript => int_var_name,
Comment on lines 255 to +256

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep ReScript here

};
let int_event_prop_as_string =
|event_prop: &str| int_as_string(format!("event.{event_prop}"));
Expand All @@ -281,9 +280,7 @@ impl Event {
match is_fuel {
true => {
let data_code = match language {
Language::ReScript => "%raw(`{}`)",
Language::TypeScript => "{}",
Language::JavaScript => "{}",
};
format!(
"{event_module}.mock({{data: {data_code} /* It mocks event fields with \
Expand Down Expand Up @@ -566,12 +563,6 @@ mod test {
#[test]
fn test_get_entity_id_code() {
const IS_FUEL: bool = true;
assert_eq!(
Event::get_entity_id_code(!IS_FUEL, &Language::ReScript),
"`${event.chainId->Belt.Int.toString}_${event.block.number->Belt.Int.\
toString}_${event.logIndex->Belt.Int.toString}`"
.to_string()
);

assert_eq!(
Event::get_entity_id_code(IS_FUEL, &Language::TypeScript),
Expand Down
10 changes: 2 additions & 8 deletions codegenerator/cli/src/hbs_templating/init_templates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ use std::path::PathBuf;
#[derive(Serialize, Debug, PartialEq)]
pub struct InitTemplates {
project_name: String,
is_rescript: bool,
is_typescript: bool,
is_javascript: bool,
envio_version: String,
//Used for the package.json reference to generated in handlers
relative_path_from_root_to_generated: String,
Expand Down Expand Up @@ -46,9 +44,7 @@ impl InitTemplates {
.context("Failed to diff generated from root path")?;
let template = InitTemplates {
project_name,
is_rescript: lang == &Language::ReScript,
is_typescript: lang == &Language::TypeScript,
is_javascript: lang == &Language::JavaScript,
envio_version,
relative_path_from_root_to_generated,
envio_api_token,
Expand All @@ -67,7 +63,7 @@ mod test {
fn test_new_init_template() {
let init_temp = InitTemplates::new(
"my-project".to_string(),
&Language::ReScript,
&Language::TypeScript,
&ParsedProjectPaths::default(),
"latest".to_string(),
None,
Expand All @@ -76,9 +72,7 @@ mod test {

let expected = InitTemplates {
project_name: "my-project".to_string(),
is_rescript: true,
is_typescript: false,
is_javascript: false,
is_typescript: true,
envio_version: "latest".to_string(),
relative_path_from_root_to_generated: "./generated".to_string(),
envio_api_token: None,
Expand Down
82 changes: 59 additions & 23 deletions codegenerator/cli/src/template_dirs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,20 @@ use std::{

pub trait Template: Display {
fn to_dir_name(&self) -> String;

}

impl Template for evm::Template {
fn to_dir_name(&self) -> String {
match self {
evm::Template::Greeter => "greeter",
evm::Template::Erc20 => "erc20",
evm::Template::TopicFiltering => "topic_filtering",
evm::Template::WildcardIndexing => "wildcard_indexing",
evm::Template::OnBlockHandler => "onblock_handler",
evm::Template::EffectsAPI => "effects_api",
evm::Template::FactoryIndexer => "factory_indexer",
evm::Template::MultichainIndexer => "multichain_indexer",
}
.to_string()
}
Expand Down Expand Up @@ -431,59 +438,88 @@ mod test {
#[test]
fn all_init_templates_exist() {
let template_dirs = TemplateDirs::new();
let lang = &Language::TypeScript;

// EVM templates
for template in evm::Template::iter() {
for lang in Language::iter() {
template_dirs
.get_template_lang_dir(&template, &lang)
.expect("static lang");
}
.expect(&format!(
"Expected static language dir for EVM template {:?} and language {:?}",
template, &lang
));


template_dirs
.get_template_shared_dir(&template)
.expect("static templte shared");
.expect(&format!(
"Expected shared static dir for EVM template {:?}",
template
));
}

// FUEL templates
for template in fuel::Template::iter() {
for lang in Language::iter() {
template_dirs
.get_template_lang_dir(&template, &lang)
.expect("static lang");
}
.expect(&format!(
"Expected static language dir for FUEL template {:?} and language {:?}",
template, &lang
));

template_dirs
.get_template_shared_dir(&template)
.expect("static templte shared");
.expect(&format!(
"Expected shared static dir for FUEL template {:?}",
template
));
}

// Shared dynamic init template
template_dirs
.get_init_template_dynamic_shared()
.expect("dynami shared init template");
.expect("Expected dynamic shared init template dir to exist");
}

#[test]
fn all_init_templates_extract_succesfully() {
let template_dirs = TemplateDirs::new();
let temp_dir =
TempDir::new("init_extract_lang_test").expect("Failed creating tempdir init template");
let temp_dir = TempDir::new("init_extract_lang_test")
.expect("Failed creating tempdir for init template");
let lang = &Language::TypeScript;

// EVM templates
for template in evm::Template::iter() {
for lang in Language::iter() {
template_dirs
.get_and_extract_template(&template, &lang, &(PathBuf::from(temp_dir.path())))
.expect("static lang");
}
.get_and_extract_template(&template, &lang, &PathBuf::from(temp_dir.path()))
.expect(&format!(
"Failed extracting EVM template {:?} for language {:?}",
template, &lang
));

}

// FUEL templates
for template in fuel::Template::iter() {
for lang in Language::iter() {
template_dirs
.get_and_extract_template(&template, &lang, &(PathBuf::from(temp_dir.path())))
.expect("static lang");
}
.get_and_extract_template(&template, &lang, &PathBuf::from(temp_dir.path()))
.expect(&format!(
"Failed extracting FUEL template {:?} for language {:?}",
template, &lang
));
}
let temp_dir =
TempDir::new("init_extract_blank_lang_test").expect("Failed creating tempdir blank");

// Blank template extraction
let blank_temp_dir = TempDir::new("init_extract_blank_lang_test")
.expect("Failed creating tempdir for blank template");

for lang in Language::iter() {
template_dirs
.get_and_extract_blank_template(&lang, &temp_dir.path().into())
.expect("static blank");
.get_and_extract_blank_template(&lang, &PathBuf::from(blank_temp_dir.path()))
.expect(&format!(
"Failed extracting blank template for language {:?}",
lang
));
}
}

Expand Down
Loading